diff --git a/.codeclimate.yml b/.codeclimate.yml index d079d891f..31da9b9cb 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -7,14 +7,19 @@ prepare: plugins: brakeman: enabled: true + checks: + mass_assign_permit!: + enabled: false bundler-audit: enabled: true duplication: enabled: true config: languages: - - ruby - - javascript + ruby: + mass_threshold: 100 + javascript: + mass_threshold: 100 eslint: enabled: true channel: eslint-5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3770abd4c..9afedbf17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +11.02.2021 +* Poll messages on locking and unlocking a domain [#1828](https://github.com/internetee/registry/issues/1828) +* Registrar's prefix is now checked and added to contact id for info and check requests [#1832](https://github.com/internetee/registry/issues/1832) + +10.02.2021 +* Admin contact bulk change option for registrars [#1764](https://github.com/internetee/registry/issues/1764) +* Option to remove email addresses from AWS SES Supression list [#1839](https://github.com/internetee/registry/issues/1839) +* Added separate key for bounce API [#1842](https://github.com/internetee/registry/pull/1842) + +09.02.2021 +* Added new endpoint for WHOIS contact requests [#1794](https://github.com/internetee/registry/pull/1794) + +05.02.2021 +* Fixed IPv4 empty string issue in case of IPv6 only entries for IP whitelist [#1833](https://github.com/internetee/registry/issues/1833) + +02.02.2021 +* Fixed updateProhibited status not affecting bulk tech contact change operation [#1820](https://github.com/internetee/registry/pull/1820) + +01.02.2021 +* Improved tests for admin interface [#1805](https://github.com/internetee/registry/pull/1805) + +28.01.2021 +* Fixed transfer with shared admin and tech contacts [#1808](https://github.com/internetee/registry/issues/1808) +* Improved error handling with double admin/tech contacts [#1758](https://github.com/internetee/registry/issues/1758) +* Added CSV export option to admin [#1775](https://github.com/internetee/registry/issues/1775) +* Improved DNSSEC key validation for illegal characters [#1790](https://github.com/internetee/registry/issues/1790) +* Fix for whois record creation issue on releasing domain to auction [#1139](https://github.com/internetee/registry/issues/1139) +* Fix for handling malformed request frames [#1825](https://github.com/internetee/registry/issues/1825) +* Improved registrar account activity tests [#1824](https://github.com/internetee/registry/pull/1824) + +27.01.2021 +* Figaro update to 1.2.0 [#1823](https://github.com/internetee/registry/pull/1823) + 26.01.2021 * Ruby update to 2.7 [#1791](https://github.com/internetee/registry/issues/1791) diff --git a/Gemfile b/Gemfile index 9bbcba254..82a0b4666 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem 'rest-client' gem 'uglifier' # load env -gem 'figaro', '1.1.1' +gem 'figaro', '~> 1.2' # model related gem 'activerecord-import' @@ -92,3 +92,5 @@ group :test do gem 'webdrivers' gem 'webmock' end + +gem 'aws-sdk-sesv2', '~> 1.16' diff --git a/Gemfile.lock b/Gemfile.lock index 2a0bb55b1..08daf4226 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -148,6 +148,18 @@ GEM attr_required (1.0.1) autoprefixer-rails (10.0.0.2) execjs + aws-eventstream (1.1.0) + aws-partitions (1.424.0) + aws-sdk-core (3.112.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-sesv2 (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.16) bindata (2.4.8) bootsnap (1.4.8) @@ -202,8 +214,8 @@ GEM erubis (2.7.0) execjs (2.7.0) ffi (1.13.1) - figaro (1.1.1) - thor (~> 0.14) + figaro (1.2.0) + thor (>= 0.14.0, < 2) globalid (0.4.2) activesupport (>= 4.2.0) gyoku (1.3.1) @@ -226,6 +238,7 @@ GEM i18n_data (0.10.0) isikukood (0.1.2) iso8601 (0.12.1) + jmespath (1.4.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -484,6 +497,7 @@ DEPENDENCIES active_interaction (~> 3.8) activerecord-import airbrake + aws-sdk-sesv2 (~> 1.16) bootsnap (>= 1.1.0) bootstrap-sass (~> 3.4) cancancan @@ -502,7 +516,7 @@ DEPENDENCIES e_invoice! epp! epp-xml (= 1.1.0)! - figaro (= 1.1.1) + figaro (~> 1.2) haml (~> 5.0) isikukood iso8601 (= 0.12.1) diff --git a/app/assets/javascripts/admin/application.coffee b/app/assets/javascripts/admin/application.coffee index 4c7e6acb2..a149d89cf 100644 --- a/app/assets/javascripts/admin/application.coffee +++ b/app/assets/javascripts/admin/application.coffee @@ -8,6 +8,8 @@ $(window).load -> $('[data-toggle="popover"]').popover() + $('[data-toggle="tooltip"]').tooltip() + # doublescroll $('[data-doublescroll]').doubleScroll({ onlyIfScroll: false, diff --git a/app/controllers/admin/domains_controller.rb b/app/controllers/admin/domains_controller.rb index 4a8e5961e..3e2d78671 100644 --- a/app/controllers/admin/domains_controller.rb +++ b/app/controllers/admin/domains_controller.rb @@ -27,8 +27,17 @@ module Admin params[:q][:name_matches] = n_cache # we don't want to show wildcards in search form end end - @domains = @domains.per(params[:results_per_page]) if params[:results_per_page].to_i.positive? + + respond_to do |format| + format.html do + @domains + end + format.csv do + raw_csv = @domains.to_csv + send_data raw_csv, filename: 'domains.csv', type: "#{Mime[:csv]}; charset=utf-8" + end + end end def show diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index b62b3e063..6ee986a48 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -11,7 +11,7 @@ module Api end def authenticate_shared_key - api_key = "Basic #{ENV['api_shared_key']}" + api_key = "Basic #{ENV['rwhois_internal_api_shared_key']}" head(:unauthorized) unless api_key == request.authorization end diff --git a/app/controllers/api/v1/bounces_controller.rb b/app/controllers/api/v1/bounces_controller.rb index fd10a3398..de2814250 100644 --- a/app/controllers/api/v1/bounces_controller.rb +++ b/app/controllers/api/v1/bounces_controller.rb @@ -1,7 +1,7 @@ module Api module V1 class BouncesController < BaseController - before_action :authenticate_shared_key + before_action :validate_shared_key_integrity # POST api/v1/bounces/ def create @@ -20,6 +20,13 @@ module Api params.require(:data) end + + private + + def validate_shared_key_integrity + api_key = "Basic #{ENV['rwhois_bounces_api_shared_key']}" + head(:unauthorized) unless api_key == request.authorization + end end end end diff --git a/app/controllers/api/v1/contact_requests_controller.rb b/app/controllers/api/v1/contact_requests_controller.rb new file mode 100644 index 000000000..2b5977f59 --- /dev/null +++ b/app/controllers/api/v1/contact_requests_controller.rb @@ -0,0 +1,37 @@ +module Api + module V1 + class ContactRequestsController < BaseController + before_action :authenticate_shared_key + + # POST api/v1/contact_requests/ + def create + return head(:bad_request) if contact_request_params[:email].blank? + + contact_request = ContactRequest.save_record(contact_request_params) + render json: contact_request, status: :created + rescue StandardError + head(:bad_request) + end + + def update + return head(:bad_request) if params[:id].blank? + + process_id(params[:id]) + end + + def process_id(id) + record = ContactRequest.find_by(id: id) + return :not_found unless record + + record.update_status(contact_request_params) + render json: record, status: :ok + rescue StandardError + head :bad_request + end + + def contact_request_params + params.require(:contact_request).permit(:email, :whois_record_id, :name, :status, :ip) + end + end + end +end diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index 85305213b..65354ff48 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -14,7 +14,7 @@ module Epp authorize! :check, Epp::Contact ids = params[:parsed_frame].css('id').map(&:text) - @results = Epp::Contact.check_availability(ids) + @results = Epp::Contact.check_availability(ids, reg: current_user.registrar.code) render_epp_response '/epp/contacts/check' end @@ -93,7 +93,11 @@ module Epp def find_contact code = params[:parsed_frame].css('id').text.strip.upcase - @contact = Epp::Contact.find_by!(code: code) + reg_code = current_user.registrar.code.upcase + arr = [code, "#{reg_code}:#{code}", "CID:#{code}", "CID:#{reg_code}:#{code}"] + + contact = arr.find { |c| Epp::Contact.find_by(code: c).present? } + @contact = Epp::Contact.find_by!(code: contact || code) end # diff --git a/app/controllers/registrar/admin_contacts_controller.rb b/app/controllers/registrar/admin_contacts_controller.rb new file mode 100644 index 000000000..a1400b6dc --- /dev/null +++ b/app/controllers/registrar/admin_contacts_controller.rb @@ -0,0 +1,18 @@ +class Registrar + class AdminContactsController < BulkChangeController + BASE_URL = URI.parse("#{ENV['repp_url']}domains/admin_contacts").freeze + ACTIVE_TAB = :admin_contact + + def update + authorize! :manage, :repp + uri = BASE_URL + request = form_request(uri) + response = do_request(request, uri) + start_notice = t('.replaced') + + process_response(response: response, + start_notice: start_notice, + active_tab: ACTIVE_TAB) + end + end +end diff --git a/app/controllers/registrar/bulk_change_controller.rb b/app/controllers/registrar/bulk_change_controller.rb index 801ab0516..74bbf89e8 100644 --- a/app/controllers/registrar/bulk_change_controller.rb +++ b/app/controllers/registrar/bulk_change_controller.rb @@ -26,6 +26,84 @@ class Registrar private + def form_request(uri) + request = Net::HTTP::Patch.new(uri) + request.set_form_data(current_contact_id: params[:current_contact_id], + new_contact_id: params[:new_contact_id]) + request.basic_auth(current_registrar_user.username, + current_registrar_user.plain_text_password) + request + end + + def process_response(response:, start_notice: '', active_tab:) + parsed_response = JSON.parse(response.body, symbolize_names: true) + + if response.code == '200' + notices = success_notices(parsed_response, start_notice) + + flash[:notice] = notices.join(', ') + redirect_to registrar_domains_url + else + @error = response.code == '404' ? 'Contact(s) not found' : parsed_response[:message] + render file: 'registrar/bulk_change/new', locals: { active_tab: active_tab } + end + end + + def success_notices(parsed_response, start_notice) + notices = [start_notice] + + notices << "#{t('.affected_domains')}: " \ + "#{parsed_response[:data][:affected_domains].join(', ')}" + + if parsed_response[:data][:skipped_domains] + notices << "#{t('.skipped_domains')}: " \ + "#{parsed_response[:data][:skipped_domains].join(', ')}" + end + notices + end + + def do_request(request, uri) + response = if Rails.env.test? + do_test_request(request, uri) + elsif Rails.env.development? + do_dev_request(request, uri) + else + do_live_request(request, uri) + end + response + end + + def do_live_request(request, uri) + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + 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 + + def do_dev_request(request, uri) + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + 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 + end + + def do_test_request(request, uri) + Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http| + http.request(request) + end + end + def ready_to_renew? domain_ids_for_bulk_renew.present? && params[:renew].present? end diff --git a/app/controllers/registrar/domain_transfers_controller.rb b/app/controllers/registrar/domain_transfers_controller.rb index 584a50d33..e055c38d8 100644 --- a/app/controllers/registrar/domain_transfers_controller.rb +++ b/app/controllers/registrar/domain_transfers_controller.rb @@ -25,32 +25,7 @@ class Registrar current_registrar_user.plain_text_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 + response = do_request(request, uri) parsed_response = JSON.parse(response.body, symbolize_names: true) diff --git a/app/controllers/registrar/nameservers_controller.rb b/app/controllers/registrar/nameservers_controller.rb index 52c43bb1d..3eb23cd48 100644 --- a/app/controllers/registrar/nameservers_controller.rb +++ b/app/controllers/registrar/nameservers_controller.rb @@ -18,32 +18,7 @@ class Registrar request.basic_auth(current_registrar_user.username, current_registrar_user.plain_text_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 + response = do_request(request, uri) parsed_response = JSON.parse(response.body, symbolize_names: true) diff --git a/app/controllers/registrar/tech_contacts_controller.rb b/app/controllers/registrar/tech_contacts_controller.rb index 001651250..cc9238730 100644 --- a/app/controllers/registrar/tech_contacts_controller.rb +++ b/app/controllers/registrar/tech_contacts_controller.rb @@ -1,62 +1,19 @@ class Registrar class TechContactsController < BulkChangeController + BASE_URL = URI.parse("#{ENV['repp_url']}domains/contacts").freeze + ACTIVE_TAB = :technical_contact + def update authorize! :manage, :repp - uri = URI.parse("#{ENV['repp_url']}domains/contacts") + uri = BASE_URL + request = form_request(uri) + response = do_request(request, uri) + start_notice = t('.replaced') - request = Net::HTTP::Patch.new(uri) - request.set_form_data(current_contact_id: params[:current_contact_id], - new_contact_id: params[:new_contact_id]) - request.basic_auth(current_registrar_user.username, - current_registrar_user.plain_text_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' - notices = [t('.replaced')] - - notices << "#{t('.affected_domains')}: " \ - "#{parsed_response[:data][:affected_domains].join(', ')}" - - if parsed_response[:data][:skipped_domains] - notices << "#{t('.skipped_domains')}: " \ - "#{parsed_response[:data][:skipped_domains].join(', ')}" - end - - flash[:notice] = notices.join(', ') - redirect_to registrar_domains_url - else - @error = response.code == '404' ? 'Contact(s) not found' : parsed_response[:message] - render file: 'registrar/bulk_change/new', locals: { active_tab: :technical_contact } - end + process_response(response: response, + start_notice: start_notice, + active_tab: ACTIVE_TAB) end end end diff --git a/app/controllers/repp/v1/domains/admin_contacts_controller.rb b/app/controllers/repp/v1/domains/admin_contacts_controller.rb new file mode 100644 index 000000000..2e9a285eb --- /dev/null +++ b/app/controllers/repp/v1/domains/admin_contacts_controller.rb @@ -0,0 +1,21 @@ +module Repp + module V1 + module Domains + class AdminContactsController < BaseContactsController + def update + super + + unless @new_contact.identical_to?(@current_contact) + @epp_errors << { code: 2304, msg: 'Admin contacts must be identical' } + end + + return handle_errors if @epp_errors.any? + + affected, skipped = AdminDomainContact.replace(@current_contact, @new_contact) + @response = { affected_domains: affected, skipped_domains: skipped } + render_success(data: @response) + end + end + end + end +end diff --git a/app/controllers/repp/v1/domains/base_contacts_controller.rb b/app/controllers/repp/v1/domains/base_contacts_controller.rb new file mode 100644 index 000000000..b601c5313 --- /dev/null +++ b/app/controllers/repp/v1/domains/base_contacts_controller.rb @@ -0,0 +1,31 @@ +module Repp + module V1 + module Domains + class BaseContactsController < BaseController + before_action :set_current_contact, only: [:update] + before_action :set_new_contact, only: [:update] + + def set_current_contact + @current_contact = current_user.registrar.contacts + .find_by!(code: contact_params[:current_contact_id]) + end + + def set_new_contact + @new_contact = current_user.registrar.contacts.find_by!(code: params[:new_contact_id]) + end + + def update + @epp_errors ||= [] + @epp_errors << { code: 2304, msg: 'New contact must be valid' } if @new_contact.invalid? + end + + private + + def contact_params + params.require(%i[current_contact_id new_contact_id]) + params.permit(:current_contact_id, :new_contact_id) + end + end + end + end +end diff --git a/app/controllers/repp/v1/domains/contacts_controller.rb b/app/controllers/repp/v1/domains/contacts_controller.rb index 75404e0c6..131615570 100644 --- a/app/controllers/repp/v1/domains/contacts_controller.rb +++ b/app/controllers/repp/v1/domains/contacts_controller.rb @@ -1,23 +1,9 @@ module Repp module V1 module Domains - class ContactsController < BaseController - before_action :set_current_contact, only: [:update] - before_action :set_new_contact, only: [:update] - - def set_current_contact - @current_contact = current_user.registrar.contacts.find_by!( - code: contact_params[:current_contact_id] - ) - end - - def set_new_contact - @new_contact = current_user.registrar.contacts.find_by!(code: params[:new_contact_id]) - end - + class ContactsController < BaseContactsController def update - @epp_errors ||= [] - @epp_errors << { code: 2304, msg: 'New contact must be valid' } if @new_contact.invalid? + super if @new_contact == @current_contact @epp_errors << { code: 2304, msg: 'New contact must be different from current' } @@ -29,13 +15,6 @@ module Repp @response = { affected_domains: affected, skipped_domains: skipped } render_success(data: @response) end - - private - - def contact_params - params.require(%i[current_contact_id new_contact_id]) - params.permit(:current_contact_id, :new_contact_id) - end end end end diff --git a/app/lib/to_stdout.rb b/app/lib/to_stdout.rb new file mode 100644 index 000000000..eeab82c15 --- /dev/null +++ b/app/lib/to_stdout.rb @@ -0,0 +1,6 @@ +class ToStdout + def self.msg(message) + time = Time.zone.now.utc + STDOUT << "#{time} - #{message}\n" unless Rails.env.test? + end +end diff --git a/app/models/admin_domain_contact.rb b/app/models/admin_domain_contact.rb index 14907403d..7ccf3efcb 100644 --- a/app/models/admin_domain_contact.rb +++ b/app/models/admin_domain_contact.rb @@ -1,2 +1,26 @@ class AdminDomainContact < DomainContact + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def self.replace(current_contact, new_contact) + affected_domains = [] + skipped_domains = [] + admin_contacts = where(contact: current_contact) + + admin_contacts.each do |admin_contact| + if admin_contact.domain.bulk_update_prohibited? + skipped_domains << admin_contact.domain.name + next + end + begin + admin_contact.contact = new_contact + admin_contact.save! + affected_domains << admin_contact.domain.name + rescue ActiveRecord::RecordNotUnique + skipped_domains << admin_contact.domain.name + end + end + [affected_domains.sort, skipped_domains.sort] + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength end diff --git a/app/models/bounced_mail_address.rb b/app/models/bounced_mail_address.rb index 73c6a0941..db4413829 100644 --- a/app/models/bounced_mail_address.rb +++ b/app/models/bounced_mail_address.rb @@ -1,5 +1,6 @@ class BouncedMailAddress < ApplicationRecord validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true + after_destroy :destroy_aws_suppression def bounce_reason "#{action} (#{status} #{diagnostic})" @@ -25,4 +26,20 @@ class BouncedMailAddress < ApplicationRecord diagnostic: bounced_record['diagnosticCode'], } end + + def destroy_aws_suppression + return unless BouncedMailAddress.ses_configured? + + res = Aws::SESV2::Client.new.delete_suppressed_destination(email_address: email) + res.successful? + rescue Aws::SESV2::Errors::ServiceError => e + logger.warn("Suppression not removed. #{e}") + end + + def self.ses_configured? + ses ||= Aws::SESV2::Client.new + ses.config.credentials.access_key_id.present? + rescue Aws::Errors::MissingRegionError + false + end end diff --git a/app/models/concerns/contact/identical.rb b/app/models/concerns/contact/identical.rb index f529e09ac..5327d1704 100644 --- a/app/models/concerns/contact/identical.rb +++ b/app/models/concerns/contact/identical.rb @@ -11,6 +11,13 @@ module Concerns::Contact::Identical ident_country_code org_name ] + + IDENTICAL_ATTRIBUTES = %w[ + ident + ident_type + ident_country_code + ].freeze + private_constant :IDENTIFIABLE_ATTRIBUTES def identical(registrar) @@ -20,6 +27,12 @@ module Concerns::Contact::Identical .where.not(id: id).take end + def identical_to?(contact) + IDENTICAL_ATTRIBUTES.all? do |attribute| + attributes[attribute] == contact.attributes[attribute] + end + end + private def identifiable_hash diff --git a/app/models/concerns/domain/bulk_updatable.rb b/app/models/concerns/domain/bulk_updatable.rb new file mode 100644 index 000000000..a0aadb95f --- /dev/null +++ b/app/models/concerns/domain/bulk_updatable.rb @@ -0,0 +1,17 @@ +module Concerns + module Domain + module BulkUpdatable + extend ActiveSupport::Concern + + def bulk_update_prohibited? + discarded? || statuses_blocks_update? + end + + def statuses_blocks_update? + prohibited_array = [DomainStatus::SERVER_UPDATE_PROHIBITED, + DomainStatus::CLIENT_UPDATE_PROHIBITED] + prohibited_array.any? { |block_status| statuses.include?(block_status) } + end + end + end +end diff --git a/app/models/concerns/domain/registry_lockable.rb b/app/models/concerns/domain/registry_lockable.rb index 4a759296d..2325e4b60 100644 --- a/app/models/concerns/domain/registry_lockable.rb +++ b/app/models/concerns/domain/registry_lockable.rb @@ -12,6 +12,7 @@ module Concerns statuses << DomainStatus::SERVER_DELETE_PROHIBITED statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED self.locked_by_registrant_at = Time.zone.now + alert_registrar_lock_changes!(lock: true) save! end @@ -42,10 +43,21 @@ module Concerns statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) self.locked_by_registrant_at = nil + alert_registrar_lock_changes!(lock: false) save! end end + + def alert_registrar_lock_changes!(lock: true) + translation = lock ? 'locked' : 'unlocked' + registrar.notifications.create!( + text: I18n.t("notifications.texts.registrar_#{translation}", + domain_name: name), + attached_obj_id: name, + attached_obj_type: self.class.name + ) + end end end end diff --git a/app/models/concerns/domain/releasable.rb b/app/models/concerns/domain/releasable.rb index 4aa5faa58..0a17b062a 100644 --- a/app/models/concerns/domain/releasable.rb +++ b/app/models/concerns/domain/releasable.rb @@ -39,13 +39,15 @@ module Concerns def release if release_to_auction - transaction do - domain_name.sell_at_auction if domain_name.auctionable? - destroy! - registrar.notifications.create!(text: "#{I18n.t(:domain_deleted)}: #{name}", - attached_obj_id: id, - attached_obj_type: self.class) - end + ToStdout.msg 'Destroying domain' + destroy! + ToStdout.msg "Checking if domain_name is auctionable: #{domain_name.auctionable?}" + domain_name.sell_at_auction if domain_name.auctionable? + + ToStdout.msg 'Sending registrar notification' + registrar.notifications.create!(text: "#{I18n.t(:domain_deleted)}: #{name}", + attached_obj_id: id, + attached_obj_type: self.class) else discard end diff --git a/app/models/concerns/domain/transferable.rb b/app/models/concerns/domain/transferable.rb index 9de2fff83..5400e9409 100644 --- a/app/models/concerns/domain/transferable.rb +++ b/app/models/concerns/domain/transferable.rb @@ -59,7 +59,7 @@ module Concerns::Domain::Transferable copied_ids = [] domain_contacts.each do |dc| contact = Contact.find(dc.contact_id) - next if copied_ids.include?(contact.id) || contact.registrar == new_registrar + next if copied_ids.include?(uniq_contact_hash(dc)) || contact.registrar == new_registrar if registrant_id_was == contact.id # registrant was copied previously, do not copy it again oc = OpenStruct.new(id: registrant_id) @@ -72,7 +72,11 @@ module Concerns::Domain::Transferable else dc.update(contact_id: oc.id) end - copied_ids << contact.id + copied_ids << uniq_contact_hash(dc) end end + + def uniq_contact_hash(contact) + Digest::SHA1.hexdigest(contact.contact_id.to_s + contact.type) + end end diff --git a/app/models/contact_request.rb b/app/models/contact_request.rb new file mode 100644 index 000000000..e6a5e9f7d --- /dev/null +++ b/app/models/contact_request.rb @@ -0,0 +1,40 @@ +class ContactRequest < ApplicationRecord + establish_connection :"whois_#{Rails.env}" + self.table_name = 'contact_requests' + + STATUS_NEW = 'new'.freeze + STATUS_CONFIRMED = 'confirmed'.freeze + STATUS_SENT = 'sent'.freeze + STATUSES = [STATUS_NEW, STATUS_CONFIRMED, STATUS_SENT].freeze + + validates :whois_record_id, presence: true + validates :email, presence: true + validates :name, presence: true + validates :status, inclusion: { in: STATUSES } + + attr_readonly :secret, + :valid_to + + def self.save_record(params) + contact_request = new(params) + contact_request.secret = create_random_secret + contact_request.valid_to = set_valid_to_24_hours_from_now + contact_request.status = STATUS_NEW + contact_request.save! + contact_request + end + + def update_status(params) + self.status = params['status'] + self.ip_address = params['ip'] + save! + end + + def self.create_random_secret + SecureRandom.hex(64) + end + + def self.set_valid_to_24_hours_from_now + (Time.zone.now + 24.hours) + end +end diff --git a/app/models/dns/domain_name.rb b/app/models/dns/domain_name.rb index c1af4d5e7..1e9cd6587 100644 --- a/app/models/dns/domain_name.rb +++ b/app/models/dns/domain_name.rb @@ -36,6 +36,7 @@ module DNS auction = Auction.new auction.domain = name auction.start + ToStdout.msg "Created the auction: #{auction.inspect}" update_whois_from_auction(auction) end @@ -100,7 +101,8 @@ module DNS whois_record = Whois::Record.find_or_create_by!(name: name) do |record| record.json = {} end - + ToStdout.msg "Starting to update WHOIS record #{whois_record.inspect}\n\n"\ + "from auction #{auction.inspect}" whois_record.update_from_auction(auction) end end diff --git a/app/models/domain.rb b/app/models/domain.rb index 88fa94fbc..53f0fa5b6 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -10,6 +10,7 @@ class Domain < ApplicationRecord include Concerns::Domain::RegistryLockable include Concerns::Domain::Releasable include Concerns::Domain::Disputable + include Concerns::Domain::BulkUpdatable attr_accessor :roles @@ -78,7 +79,7 @@ class Domain < ApplicationRecord true end - after_commit :update_whois_record, unless: -> { domain_name.at_auction? } + after_commit :update_whois_record after_create :update_reserved_domains def update_reserved_domains diff --git a/app/models/epp/contact.rb b/app/models/epp/contact.rb index 50ebac065..0c0ed3d5f 100644 --- a/app/models/epp/contact.rb +++ b/app/models/epp/contact.rb @@ -42,17 +42,12 @@ class Epp::Contact < Contact ) end - def check_availability(codes) + def check_availability(codes, reg:) codes = [codes] if codes.is_a?(String) - res = [] - codes.each do |x| - contact = find_by_epp_code(x) - if contact - res << { code: contact.code, avail: 0, reason: 'in use' } - else - res << { code: x, avail: 1 } - end + codes.map { |c| c.include?(':') ? c : "#{reg}:#{c}" }.map { |c| c.strip.upcase }.each do |x| + c = find_by_epp_code(x) + res << (c ? { code: c.code, avail: 0, reason: 'in use' } : { code: x, avail: 1 }) end res diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb index d3a57df1f..ad96adc64 100644 --- a/app/models/epp/domain.rb +++ b/app/models/epp/domain.rb @@ -162,6 +162,9 @@ class Epp::Domain < Domain at[:admin_domain_contacts_attributes] = admin_domain_contacts_attrs(frame, action) at[:tech_domain_contacts_attributes] = tech_domain_contacts_attrs(frame, action) + check_for_same_contacts(at[:admin_domain_contacts_attributes], 'admin') + check_for_same_contacts(at[:tech_domain_contacts_attributes], 'tech') + pw = frame.css('authInfo > pw').text at[:transfer_code] = pw if pw.present? @@ -176,6 +179,11 @@ class Epp::Domain < Domain at end + def check_for_same_contacts(contacts, contact_type) + return unless contacts.uniq.count != contacts.count + + add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid]) + end # Adding legal doc to domain and # if something goes wrong - raise Rollback error @@ -312,6 +320,7 @@ class Epp::Domain < Domain keys = [] return keys if frame.blank? inf_data = DnsSecKeys.new(frame) + add_epp_error('2005', nil, nil, %i[dnskeys invalid]) if not_base64?(inf_data) if action == 'rem' && frame.css('rem > all').first.try(:text) == 'true' @@ -333,6 +342,16 @@ class Epp::Domain < Domain errors.any? ? [] : keys end + def not_base64?(inf_data) + inf_data.key_data.any? do |key| + value = key[:public_key] + + !value.is_a?(String) || Base64.strict_encode64(Base64.strict_decode64(value)) != value + end + rescue ArgumentError + true + end + class DnsSecKeys def initialize(frame) @key_data = [] @@ -381,7 +400,7 @@ class Epp::Domain < Domain def key_data_from(frame) xm_copy frame, KEY_INTERFACE - end + end def ds_data_from(frame) frame.css('dsData').each do |ds_data| diff --git a/app/models/notification.rb b/app/models/notification.rb index e83b2c9da..07e824367 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -25,6 +25,10 @@ class Notification < ApplicationRecord '' end + def registry_lock? + text.include?('has been locked') || text.include?('has been unlocked') + end + private def set_defaults diff --git a/app/models/tech_domain_contact.rb b/app/models/tech_domain_contact.rb index 92799061c..20f21b6ed 100644 --- a/app/models/tech_domain_contact.rb +++ b/app/models/tech_domain_contact.rb @@ -6,7 +6,7 @@ class TechDomainContact < DomainContact tech_contacts = where(contact: current_contact) tech_contacts.each do |tech_contact| - if tech_contact.domain.discarded? + if tech_contact.domain.bulk_update_prohibited? skipped_domains << tech_contact.domain.name next end @@ -18,7 +18,6 @@ class TechDomainContact < DomainContact skipped_domains << tech_contact.domain.name end end - [affected_domains.sort, skipped_domains.sort] end end diff --git a/app/models/white_ip.rb b/app/models/white_ip.rb index 417633b12..38cee7b6b 100644 --- a/app/models/white_ip.rb +++ b/app/models/white_ip.rb @@ -4,8 +4,13 @@ class WhiteIp < ApplicationRecord validate :valid_ipv4? validate :valid_ipv6? - validate :validate_ipv4_and_ipv6 + before_save :normalize_blank_values + + def normalize_blank_values + %i[ipv4 ipv6].each { |c| self[c].present? || self[c] = nil } + end + def validate_ipv4_and_ipv6 return if ipv4.present? || ipv6.present? errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present)) @@ -50,10 +55,10 @@ class WhiteIp < ApplicationRecord def ids_including(ip) ipv4 = ipv6 = [] if check_ip4(ip).present? - ipv4 = select { |white_ip| IPAddr.new(white_ip.ipv4, Socket::AF_INET) === check_ip4(ip) } + ipv4 = select { |white_ip| check_ip4(white_ip.ipv4) === check_ip4(ip) } end if check_ip6(ip).present? - ipv6 = select { |white_ip| IPAddr.new(white_ip.ipv6, Socket::AF_INET6) === check_ip6(ip) } + ipv6 = select { |white_ip| check_ip6(white_ip.ipv6) === check_ip6(ip) } end (ipv4 + ipv6).pluck(:id).flatten.uniq end diff --git a/app/models/whois/record.rb b/app/models/whois/record.rb index 1d827e22a..dc9cc2ba0 100644 --- a/app/models/whois/record.rb +++ b/app/models/whois/record.rb @@ -2,23 +2,34 @@ module Whois class Record < Whois::Server self.table_name = 'whois_records' + def self.without_auctions + ids = Whois::Record.all.select { |record| Auction.where(domain: record.name).blank? } + .pluck(:id) + Whois::Record.where(id: ids) + end + def self.disclaimer Setting.registry_whois_disclaimer end + # rubocop:disable Metrics/AbcSize def update_from_auction(auction) if auction.started? update!(json: { name: auction.domain, status: ['AtAuction'], disclaimer: self.class.disclaimer }) + ToStdout.msg "Updated from auction WHOIS record #{inspect}" elsif auction.no_bids? + ToStdout.msg "Destroying WHOIS record #{inspect}" destroy! elsif auction.awaiting_payment? || auction.payment_received? update!(json: { name: auction.domain, status: ['PendingRegistration'], disclaimer: self.class.disclaimer, registration_deadline: auction.whois_deadline }) + ToStdout.msg "Updated from auction WHOIS record #{inspect}" end end + # rubocop:enable Metrics/AbcSize end end diff --git a/app/models/whois_record.rb b/app/models/whois_record.rb index 3563b9630..19805d583 100644 --- a/app/models/whois_record.rb +++ b/app/models/whois_record.rb @@ -97,7 +97,7 @@ class WhoisRecord < ApplicationRecord end def destroy_whois_record - Whois::Record.where(name: name).delete_all + Whois::Record.without_auctions.where(name: name).delete_all end private diff --git a/app/views/admin/domains/_search_form.html.erb b/app/views/admin/domains/_search_form.html.erb index bc317ea0b..1b1f50491 100644 --- a/app/views/admin/domains/_search_form.html.erb +++ b/app/views/admin/domains/_search_form.html.erb @@ -6,7 +6,7 @@ <%= f.search_field :name_matches, value: params[:q][:name_matches], class: 'form-control', placeholder: t(:name) %> -
+
<%= f.label :registrant_ident, for: nil %> <%= f.search_field :registrant_ident_eq, class: 'form-control', placeholder: t(:registrant_ident) %> @@ -18,7 +18,7 @@ <%= f.search_field :contacts_ident_eq, class: 'form-control', placeholder: t(:contact_ident) %>
-
+
<%= f.label :nameserver_hostname, for: nil %> <%= f.search_field :nameservers_hostname_eq, class: 'form-control', placeholder: t(:nameserver_hostname) %> @@ -26,7 +26,7 @@
-
+
<%= f.label :registrar_name, for: nil %> <%= f.select :registrar_id_eq, Registrar.all.map { |x| [x, x.id] }, { include_blank: true }, class: 'form-control selectize' %> @@ -38,7 +38,7 @@ <%= f.search_field :valid_to_gteq, value: params[:q][:valid_to_gteq], class: 'form-control js-datepicker', placeholder: t(:valid_to_from) %>
-
+
<%= f.label :valid_to_until, for: nil %> <%= f.search_field :valid_to_lteq, value: params[:q][:valid_to_lteq], class: 'form-control js-datepicker', placeholder: t(:valid_to_until) %> @@ -46,7 +46,7 @@
-
+
<%= label_tag :status, nil, for: nil %> <%= select_tag :statuses_contains, options_for_select(DomainStatus::STATUSES, params[:statuses_contains]), { multiple: true, class: 'form-control js-combobox' } %> @@ -58,13 +58,17 @@ <%= text_field_tag :results_per_page, params[:results_per_page], class: 'form-control', placeholder: t(:results_per_page) %>
-
+
+ <%= link_to t('.download_csv_btn'), admin_domains_path(format: :csv, params: params.permit!), + "data-toggle" => "tooltip", "data-placement" => "bottom", "title" => t('.download_csv_btn'), + class: 'btn btn-default' %> <%= link_to t('.reset_btn'), admin_domains_path, class: 'btn btn-default' %> +
<% end %> diff --git a/app/views/admin/repp_logs/show.haml b/app/views/admin/repp_logs/show.haml index bbaae977f..9bb9ea52e 100644 --- a/app/views/admin/repp_logs/show.haml +++ b/app/views/admin/repp_logs/show.haml @@ -1,6 +1,6 @@ - content_for :actions do = link_to(t(:back), :back, class: 'btn btn-primary') -= render 'shared/title', name: t(:repp_log) += render 'shared/title', name: t('.title') .row .col-md-12 diff --git a/app/views/epp/poll/poll_req.xml.builder b/app/views/epp/poll/poll_req.xml.builder index 664327dae..a58b082c5 100644 --- a/app/views/epp/poll/poll_req.xml.builder +++ b/app/views/epp/poll/poll_req.xml.builder @@ -15,12 +15,22 @@ xml.epp_head do end if @object end - if @notification.action&.contact - render(partial: 'epp/poll/action', - locals: { - builder: xml, - action: @notification.action - }) + if @notification.action&.contact || @notification.registry_lock? + if @notification.registry_lock? + state = @notification.text.include?('unlocked') ? 'unlock' : 'lock' + xml.extension do + xml.tag!('changePoll:changeData', + 'xmlns:changePoll': 'https://epp.tld.ee/schema/changePoll-1.0.xsd') do + xml.tag!('changePoll:operation', state) + end + end + else + render(partial: 'epp/poll/action', + locals: { + builder: xml, + action: @notification.action, + }) + end end render('epp/shared/trID', builder: xml) diff --git a/app/views/registrar/bulk_change/_admin_contact_form.html.erb b/app/views/registrar/bulk_change/_admin_contact_form.html.erb new file mode 100644 index 000000000..77734e872 --- /dev/null +++ b/app/views/registrar/bulk_change/_admin_contact_form.html.erb @@ -0,0 +1,65 @@ +<%= form_tag registrar_admin_contacts_path, method: :patch, class: 'form-horizontal' do %> + <% if @error %> +
+ <%= @error %> +
+ <% end %> + +
+
+
+

<%= t '.comment' %>

+
+
+
+ <%= label_tag :current_contact_id, t('.current_contact_id') %> +
+ +
+ <%= text_field_tag :current_contact_id, params[:current_contact_id], + list: :contacts, + required: true, + autofocus: true, + class: 'form-control' %> +
+
+ +
+
+ <%= label_tag :new_contact_id, t('.new_contact_id') %> +
+ +
+ <%= text_field_tag :new_contact_id, params[:new_contact_id], + list: :contacts, + required: true, + class: 'form-control' %> +
+
+ +
+
+ +
+
+ +
+
+ <%= t '.help_btn' %> +
+
+ <%= t '.help' %> +
+
+
+
+<% end %> + + + <% available_contacts.each do |data| %> + + <% end %> + diff --git a/app/views/registrar/bulk_change/_tech_contact_form.html.erb b/app/views/registrar/bulk_change/_tech_contact_form.html.erb index 2848e3634..789db92ba 100644 --- a/app/views/registrar/bulk_change/_tech_contact_form.html.erb +++ b/app/views/registrar/bulk_change/_tech_contact_form.html.erb @@ -10,7 +10,7 @@ <%= label_tag :current_contact_id, t('.current_contact_id') %>
-
+
<%= text_field_tag :current_contact_id, params[:current_contact_id], list: :contacts, required: true, @@ -24,7 +24,7 @@ <%= label_tag :new_contact_id, t('.new_contact_id') %>
-
+
<%= text_field_tag :new_contact_id, params[:new_contact_id], list: :contacts, required: true, diff --git a/app/views/registrar/bulk_change/new.html.erb b/app/views/registrar/bulk_change/new.html.erb index e61270b6d..f3095a53d 100644 --- a/app/views/registrar/bulk_change/new.html.erb +++ b/app/views/registrar/bulk_change/new.html.erb @@ -12,6 +12,10 @@ <%= t '.technical_contact' %> +
  • + <%= t '.admin_contact' %> +
  • +
  • <%= t '.nameserver' %>
  • @@ -31,6 +35,11 @@ <%= render 'tech_contact_form', available_contacts: available_contacts %>
    +
    + <%= render 'admin_contact_form', available_contacts: available_contacts %> +
    +
    <%= render 'nameserver_form' %>
    diff --git a/config/application.rb b/config/application.rb index a5fb17c9d..014c03269 100644 --- a/config/application.rb +++ b/config/application.rb @@ -36,8 +36,10 @@ module DomainNameRegistry # Autoload all model subdirs config.autoload_paths += Dir[Rails.root.join('app', 'models', '**/')] + config.autoload_paths += Dir[Rails.root.join('app', 'lib', '**/')] config.autoload_paths += Dir[Rails.root.join('app', 'interactions', '**/')] config.eager_load_paths << config.root.join('lib', 'validators') + config.eager_load_paths << config.root.join('app', 'lib') config.watchable_dirs['lib'] = %i[rb] config.active_record.schema_format = :sql diff --git a/config/application.yml.sample b/config/application.yml.sample index 5885c47a2..dd38e206c 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -87,8 +87,11 @@ sk_digi_doc_service_name: 'Testimine' registrant_api_base_url: registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas -# Bounces API -api_shared_key: testkey +# Shared key for REST-WHOIS Bounces API incl. CERT +rwhois_bounces_api_shared_key: testkey + +# Link to REST-WHOIS API +rwhois_internal_api_shared_key: testkey # Base URL (inc. https://) of REST registrant portal # Leave blank to use internal registrant portal diff --git a/config/initializers/aws_ses.rb b/config/initializers/aws_ses.rb new file mode 100644 index 000000000..baa148e65 --- /dev/null +++ b/config/initializers/aws_ses.rb @@ -0,0 +1,4 @@ +Aws.config.update( + region: ENV['aws_default_region'], + credentials: Aws::Credentials.new(ENV['aws_access_key_id'], ENV['aws_secret_access_key']) +) diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index f2acc88e6..4f4e68288 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,9 +1,9 @@ Rails.application.configure do config.filter_parameters += [:password, /^frame$/, /^nokogiri_frame$/, /^parsed_frame$/] config.filter_parameters << lambda do |key, value| - if key == 'raw_frame' - value.to_s.gsub!(/pw>.+<\//, 'pw>[FILTERED]]+)>([^<])+<\/eis:legalDocument>/, + if key == 'raw_frame' && value.respond_to?(:gsub!) + value.gsub!(/pw>.+<\//, 'pw>[FILTERED]]+)>([^<])+<\/eis:legalDocument>/, "[FILTERED]") end end diff --git a/config/locales/admin/domains.en.yml b/config/locales/admin/domains.en.yml index c6e96bb15..ce59294dd 100644 --- a/config/locales/admin/domains.en.yml +++ b/config/locales/admin/domains.en.yml @@ -11,6 +11,7 @@ en: search_form: reset_btn: Reset + download_csv_btn: CSV form: pending_delete: &pending_delete diff --git a/config/locales/admin/repp_logs.en.yml b/config/locales/admin/repp_logs.en.yml index 559ae234a..0a58fe7ba 100644 --- a/config/locales/admin/repp_logs.en.yml +++ b/config/locales/admin/repp_logs.en.yml @@ -4,3 +4,6 @@ en: index: title: REPP log reset_btn: Reset + show: + title: REPP log + diff --git a/config/locales/notifications.en.yml b/config/locales/notifications.en.yml index 1dff4a97c..d67fb5a5b 100644 --- a/config/locales/notifications.en.yml +++ b/config/locales/notifications.en.yml @@ -6,3 +6,5 @@ en: It was associated with registrant %{old_registrant_code} and contacts %{old_contacts_codes}. contact_update: Contact %{contact} has been updated by registrant + registrar_locked: Domain %{domain_name} has been locked by registrant + registrar_unlocked: Domain %{domain_name} has been unlocked by registrant diff --git a/config/locales/registrar/admin_contacts.en.yml b/config/locales/registrar/admin_contacts.en.yml new file mode 100644 index 000000000..9265a6d10 --- /dev/null +++ b/config/locales/registrar/admin_contacts.en.yml @@ -0,0 +1,6 @@ +en: + registrar: + admin_contacts: + update: + replaced: Admin contacts have been successfully replaced. + replaced: Technical contacts have been successfully replaced. diff --git a/config/locales/registrar/admin_contacts.yml b/config/locales/registrar/admin_contacts.yml new file mode 100644 index 000000000..d258b4275 --- /dev/null +++ b/config/locales/registrar/admin_contacts.yml @@ -0,0 +1,11 @@ +en: + registrar: + admin_contacts: + update: + replaced: Admin contacts have been successfully replaced. + affected_domains: Affected domains + skipped_domains: Skipped domains + process_request: + affected_domains: Affected domains + skipped_domains: Skipped domains + replaced: Admin contacts have been successfully replaced. diff --git a/config/locales/registrar/bulk_change.en.yml b/config/locales/registrar/bulk_change.en.yml index 75becfada..d9f6ebbd2 100644 --- a/config/locales/registrar/bulk_change.en.yml +++ b/config/locales/registrar/bulk_change.en.yml @@ -4,6 +4,7 @@ en: new: header: Bulk change technical_contact: Technical contact + admin_contact: Admin contact nameserver: Nameserver bulk_transfer: Bulk transfer bulk_renew: Bulk renew @@ -17,6 +18,19 @@ en: Replace technical contact specified in "current contact ID" with the one in "new contact ID" on any domain registered under this registrar + admin_contact_form: + current_contact_id: Current admin contact ID + new_contact_id: New admin contact ID + submit_btn: Replace admin contacts + help_btn: Toggle help + help: >- + Replace admin contact specified in "current contact ID" with the one in "new + contact ID" on any domain registered under this registrar. Contact idents must + be the same + comment: >- + Bulk admin change is only allowed in case of old and new contact are sharing identical + ident data ie for updating contact information. + nameserver_form: ip_hint: One IP per line replace_btn: Replace nameserver @@ -38,3 +52,5 @@ en: domain_ids: Domains for bulk renewal current_balance: Current balance period: Period + affected_domains: Affected domains + skipped_domains: Skipped domains diff --git a/config/locales/registrar/tech_contacts.en.yml b/config/locales/registrar/tech_contacts.en.yml index bf57f0cc7..b6e5d041b 100644 --- a/config/locales/registrar/tech_contacts.en.yml +++ b/config/locales/registrar/tech_contacts.en.yml @@ -5,3 +5,7 @@ en: replaced: Technical contacts have been successfully replaced. affected_domains: Affected domains skipped_domains: Skipped domains + process_request: + affected_domains: Affected domains + skipped_domains: Skipped domains + replaced: Technical contacts have been successfully replaced. diff --git a/config/routes.rb b/config/routes.rb index 3042eced4..cfc88851c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -64,6 +64,7 @@ Rails.application.routes.draw do get ':id/transfer_info', to: 'domains#transfer_info', constraints: { id: /.*/ } post 'transfer', to: 'domains#transfer' patch 'contacts', to: 'domains/contacts#update' + patch 'admin_contacts', to: 'domains/admin_contacts#update' post 'renew/bulk', to: 'domains/renews#bulk_renew' end end @@ -91,6 +92,7 @@ Rails.application.routes.draw do end resources :auctions, only: %i[index show update], param: :uuid + resources :contact_requests, only: %i[create update], param: :id resources :bounces, only: %i[create] end @@ -136,6 +138,7 @@ Rails.application.routes.draw do resource :bulk_change, controller: :bulk_change, only: :new post '/bulk_renew/new', to: 'bulk_change#bulk_renew', as: :bulk_renew resource :tech_contacts, only: :update + resource :admin_contacts, only: :update resource :nameservers, only: :update resources :contacts, constraints: {:id => /[^\/]+(?=#{ ActionController::Renderers::RENDERERS.map{|e| "\\.#{e}\\z"}.join("|") })|[^\/]+/} do member do diff --git a/doc/repp/v1/admin_contacts.md b/doc/repp/v1/admin_contacts.md new file mode 100644 index 000000000..3b78829d0 --- /dev/null +++ b/doc/repp/v1/admin_contacts.md @@ -0,0 +1,30 @@ +# Admin domain contacts + +## PATCH https://repp.internet.ee/v1/domains/admin_contacts +Replaces admin domain contacts of the current registrar. + +### Example request +``` +PATCH /repp/v1/domains/admin_contacts HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Basic dGVzdDp0ZXN0dGVzdA== + +{ + "current_contact_id": "ATSAA:749AA80F", + "new_contact_id": "ATSAA:E36957D7" +} +``` +### Example response +``` +{ + "code": 1000, + "message": "Command completed successfully", + "data": { + "affected_domains": [ + "private.ee", + ], + "skipped_domains": [] + } +} +``` diff --git a/doc/repp/v1/domain_contacts.md b/doc/repp/v1/domain_contacts.md index 2e542bf81..8c3c9b9de 100644 --- a/doc/repp/v1/domain_contacts.md +++ b/doc/repp/v1/domain_contacts.md @@ -1,7 +1,7 @@ -# Domain contacts +# Tech domain contacts ## PATCH https://repp.internet.ee/v1/domains/contacts -Replaces all domain contacts of the current registrar. +Replaces technical domain contacts of the current registrar. ### Example request ``` diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index b31489691..726767390 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -35,7 +35,6 @@ class JavaScriptApplicationSystemTestCase < ApplicationSystemTestCase def setup DatabaseCleaner.start - super Capybara.current_driver = :chrome diff --git a/test/fixtures/contact_requests.yml b/test/fixtures/contact_requests.yml new file mode 100644 index 000000000..030a4d726 --- /dev/null +++ b/test/fixtures/contact_requests.yml @@ -0,0 +1,8 @@ +new: + whois_record_id: 1 + email: aaa@bbb.com + name: Testname + status: new + secret: somesecret + valid_to: 2010-07-05 + diff --git a/test/integration/admin_area/account_activities_test.rb b/test/integration/admin_area/account_activities_test.rb new file mode 100644 index 000000000..306935269 --- /dev/null +++ b/test/integration/admin_area/account_activities_test.rb @@ -0,0 +1,39 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaAccountActivitiesIntegrationTest < ApplicationSystemTestCase + # /admin/account_activities + setup do + sign_in users(:admin) + @original_default_language = Setting.default_language + end + + def test_show_account_activities_page + account_activities(:one).update(sum: "123.00") + visit admin_account_activities_path + assert_text 'Account activities' + end + + def test_default_url_params + account_activities(:one).update(sum: "123.00") + visit admin_root_path + click_link_or_button 'Settings', match: :first + find(:xpath, "//ul/li/a[text()='Account activities']").click + + assert has_current_path?(admin_account_activities_path(created_after: 'today')) + end + + def test_download_account_activity + now = Time.zone.parse('2010-07-05 08:00') + travel_to now + account_activities(:one).update(sum: "123.00") + + get admin_account_activities_path(format: :csv) + + assert_response :ok + assert_equal "text/csv", response.headers['Content-Type'] + assert_equal %(attachment; filename="account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"; filename*=UTF-8''account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv), + response.headers['Content-Disposition'] + assert_not_empty response.body + end +end diff --git a/test/integration/admin_area/admin_users_test.rb b/test/integration/admin_area/admin_users_test.rb new file mode 100644 index 000000000..89b9edef9 --- /dev/null +++ b/test/integration/admin_area/admin_users_test.rb @@ -0,0 +1,101 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaAdminUsersIntegrationTest < JavaScriptApplicationSystemTestCase + include Devise::Test::IntegrationHelpers + include ActionView::Helpers::NumberHelper + + setup do + WebMock.allow_net_connect! + @original_default_language = Setting.default_language + sign_in users(:admin) + end + + def test_create_new_admin_user + createNewAdminUser(true) + end + + def test_create_with_invalid_data_new_admin_user + createNewAdminUser(false) + end + + def test_edit_successfully_exist_record + createNewAdminUser(true) + + visit admin_admin_users_path + click_on 'test_user_name' + + assert_text 'General' + click_on 'Edit' + + fill_in 'Password', with: 'test_password' + fill_in 'Password confirmation', with: 'test_password' + + click_on 'Save' + assert_text 'Record updated' + end + + def test_edit_exist_record_with_invalid_data + createNewAdminUser(true) + + visit admin_admin_users_path + click_on 'test_user_name' + + assert_text 'General' + click_on 'Edit' + + fill_in 'Password', with: 'test_password' + fill_in 'Password confirmation', with: 'test_password2' + + click_on 'Save' + assert_text 'Failed to update record' + end + + def test_delete_exist_record + createNewAdminUser(true) + + visit admin_admin_users_path + click_on 'test_user_name' + assert_text 'General' + click_on 'Delete' + + page.driver.browser.switch_to.alert.accept + + assert_text 'Record deleted' + end + + private + + def createNewAdminUser(valid) + visit admin_admin_users_path + click_on 'New admin user' + + fill_in 'Username', with: 'test_user_name' + # If valid=true creating valid user, if else, then with invalid data + if valid + fill_in 'Password', with: 'test_password' + fill_in 'Password confirmation', with: 'test_password' + else + fill_in 'Password', with: 'test_password' + fill_in 'Password confirmation', with: 'test_password2' + end + fill_in 'Identity code', with: '38903110313' + fill_in 'Email', with: 'oleg@tester.ee' + + select 'Estonia', from: 'admin_user_country_code', match: :first + + select_element = find(:xpath, "/html/body/div[2]/form/div[2]/div/div[7]/div[2]/div/div[1]") + select_element.click + + option_element = find(:xpath, "/html/body/div[2]/form/div[2]/div/div[7]/div[2]/div/div[2]/div/div[1]") + option_element.click + + click_on 'Save' + + if valid + assert_text 'Record created' + else + assert_text 'Failed to create record' + end + end +end diff --git a/test/integration/admin_area/blocked_domains_test.rb b/test/integration/admin_area/blocked_domains_test.rb new file mode 100644 index 000000000..7df9d30e7 --- /dev/null +++ b/test/integration/admin_area/blocked_domains_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' +require 'application_system_test_case' + + +# /admin/blocked_domains +class AdminAreaBlockedDomainsIntegrationTest < JavaScriptApplicationSystemTestCase + setup do + WebMock.allow_net_connect! + sign_in users(:admin) + @domain = domains(:shop) + @blocked_domain = blocked_domains(:one) + end + + def test_page_successfully_loaded + visit_admin_blocked_domains_path + end + + def test_add_into_blocked_list + visit_admin_blocked_domains_path + add_domain_into_blocked_list(true) + end + + def test_add_into_blocked_list_same_domain + visit_admin_blocked_domains_path + add_domain_into_blocked_list(true) + add_domain_into_blocked_list(false) + end + + def test_delete_domain_from_blocked_list + visit_admin_blocked_domains_path + add_domain_into_blocked_list(true) + + click_link_or_button 'Delete', match: :first + + # Accept to delete in modal window + page.driver.browser.switch_to.alert.accept + + assert_text 'Domain deleted!' + end + + def test_find_blocked_domain_from_blocked_list + visit_admin_blocked_domains_path + add_domain_into_blocked_list(true) + + fill_in 'Name', with: @domain.name + find(:xpath, "//span[@class='glyphicon glyphicon-search']").click + + assert_text @domain.name + end + + private + + def visit_admin_blocked_domains_path + visit admin_blocked_domains_path + assert_text 'Blocked domains' + end + + def add_domain_into_blocked_list(value) + click_on 'New blocked domain' + assert_text 'Add domain to blocked list' + + fill_in 'Name', with: @domain.name + click_on 'Save' + + return assert_text 'Domain added!' if value + return assert_text 'Failed to add domain!' + end +end diff --git a/test/integration/admin_area/certificates_test.rb b/test/integration/admin_area/certificates_test.rb new file mode 100644 index 000000000..d2eec0bc4 --- /dev/null +++ b/test/integration/admin_area/certificates_test.rb @@ -0,0 +1,71 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaCertificatesIntegrationTest < JavaScriptApplicationSystemTestCase + + setup do + WebMock.allow_net_connect! + sign_in users(:admin) + + @apiuser = users(:api_bestnames) + @certificate = certificates(:api) + @certificate.update!(csr: "-----BEGIN CERTIFICATE REQUEST-----\nMIICszCCAZsCAQAwbjELMAkGA1UEBhMCRUUxFDASBgNVBAMMC2ZyZXNoYm94LmVl\nMRAwDgYDVQQHDAdUYWxsaW5uMREwDwYDVQQKDAhGcmVzaGJveDERMA8GA1UECAwI\nSGFyanVtYWExETAPBgNVBAsMCEZyZXNoYm94MIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA1VVESynZoZhIbe8s9zHkELZ/ZDCGiM2Q8IIGb1IOieT5U2mx\nIsVXz85USYsSQY9+4YdEXnupq9fShArT8pstS/VN6BnxdfAiYXc3UWWAuaYAdNGJ\nDr5Jf6uMt1wVnCgoDL7eJq9tWMwARC/viT81o92fgqHFHW0wEolfCmnpik9o0ACD\nFiWZ9IBIevmFqXtq25v9CY2cT9+eZW127WtJmOY/PKJhzh0QaEYHqXTHWOLZWpnp\nHH4elyJ2CrFulOZbHPkPNB9Nf4XQjzk1ffoH6e5IVys2VV5xwcTkF0jY5XTROVxX\nlR2FWqic8Q2pIhSks48+J6o1GtXGnTxv94lSDwIDAQABoAAwDQYJKoZIhvcNAQEL\nBQADggEBAEFcYmQvcAC8773eRTWBJJNoA4kRgoXDMYiiEHih5iJPVSxfidRwYDTF\nsP+ttNTUg3JocFHY75kuM9T2USh+gu/trRF0o4WWa+AbK3JbbdjdT1xOMn7XtfUU\nZ/f1XCS9YdHQFCA6nk4Z+TLWwYsgk7n490AQOiB213fa1UIe83qIfw/3GRqRUZ7U\nwIWEGsHED5WT69GyxjyKHcqGoV7uFnqFN0sQVKVTy/NFRVQvtBUspCbsOirdDRie\nAB2KbGHL+t1QrRF10szwCJDyk5aYlVhxvdI8zn010nrxHkiyQpDFFldDMLJl10BW\n2w9PGO061z+tntdRcKQGuEpnIr9U5Vs=\n-----END CERTIFICATE REQUEST-----\n") + end + + def test_show_certificate_info + show_certificate_info + end + + def test_destroy_certificate + show_certificate_info + find(:xpath, "//a[text()='Delete']").click + + page.driver.browser.switch_to.alert.accept + + assert_text 'Record deleted' + end + + def test_download_csr + get download_csr_admin_api_user_certificate_path(api_user_id: @apiuser.id, id: @certificate.id) + + assert_response :ok + assert_equal 'application/octet-stream', response.headers['Content-Type'] + assert_equal "attachment; filename=\"test_bestnames.csr.pem\"; filename*=UTF-8''test_bestnames.csr.pem", response.headers['Content-Disposition'] + assert_not_empty response.body + end + + def test_download_crt + get download_crt_admin_api_user_certificate_path(api_user_id: @apiuser.id, id: @certificate.id) + + assert_response :ok + assert_equal 'application/octet-stream', response.headers['Content-Type'] + assert_equal "attachment; filename=\"test_bestnames.crt.pem\"; filename*=UTF-8''test_bestnames.crt.pem", response.headers['Content-Disposition'] + assert_not_empty response.body + end + + def test_failed_to_revoke_certificate + show_certificate_info + + find(:xpath, "//a[text()='Revoke this certificate']").click + assert_text 'Failed to update record' + end + + def test_new_api_user + visit new_admin_registrar_api_user_path(registrar_id: registrars(:bestnames).id) + + fill_in 'Username', with: 'testapiuser' + fill_in 'Password', with: 'secretpassword' + fill_in 'Identity code', with: '60305062718' + + click_on 'Create API user' + + assert_text 'API user has been successfully created' + end + + private + + def show_certificate_info + visit admin_api_user_certificate_path(api_user_id: @apiuser.id, id: @certificate.id) + assert_text 'Certificates' + end +end diff --git a/test/integration/admin_area/epp_logs_test.rb b/test/integration/admin_area/epp_logs_test.rb new file mode 100644 index 000000000..5ad514cb8 --- /dev/null +++ b/test/integration/admin_area/epp_logs_test.rb @@ -0,0 +1,52 @@ +# admin_epp_logs_path +require 'test_helper' +require 'application_system_test_case' + +class AdminEppLogsIntegrationTest < ApplicationSystemTestCase + setup do + sign_in users(:admin) + end + + def test_visit_epp_logs_page + visit admin_epp_logs_path + assert_text 'EPP log' + end + + def test_show_epp_log_page + visit admin_epp_logs_path + send_epp_request_hello + visit admin_epp_logs_path + + find(:xpath, "//tbody/tr/td/a", match: :first).click + assert_text 'Details' + end + + def test_dates_sort + Capybara.exact = true + visit admin_epp_logs_path + send_epp_request_hello + visit admin_epp_logs_path + + find(:xpath, "//a[contains(text(), 'Created at')]", match: :first).click + find(:xpath, "//a[contains(text(), 'Created at')]", match: :first).click + + epp_log_date = find(:xpath, "//table/tbody/tr/td[6]", match: :first).text(:all) + date_now = Date.today.to_s(:db) + + assert_match /#{date_now}/, epp_log_date + end + + private + + def send_epp_request_hello + request_xml = <<-XML + + + + + XML + + get epp_hello_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=non-existent' } + end +end diff --git a/test/integration/admin_area/invoices_test.rb b/test/integration/admin_area/invoices_test.rb index 887f57212..2aa17201d 100644 --- a/test/integration/admin_area/invoices_test.rb +++ b/test/integration/admin_area/invoices_test.rb @@ -6,6 +6,27 @@ class AdminAreaInvoicesIntegrationTest < ApplicationIntegrationTest sign_in users(:admin) end + def test_create_new_invoice + visit new_admin_invoice_path + + assert_text 'Create new invoice' + select 'Best Names', from: 'deposit_registrar_id', match: :first + fill_in 'Amount', with: '1000' + click_on 'Save' + + assert_equal page.status_code, 200 + end + + def test_visit_list_of_invoices_pages + visit admin_invoices_path + assert_text 'Invoices' + end + + def test_visit_invoice_page + visit admin_invoices_path(id: @invoice.id) + assert_text "Invoice no. #{@invoice.number}" + end + def test_downloads_invoice assert_equal 1, @invoice.number diff --git a/test/integration/admin_area/pending_delete_test.rb b/test/integration/admin_area/pending_delete_test.rb new file mode 100644 index 000000000..737d12480 --- /dev/null +++ b/test/integration/admin_area/pending_delete_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaPendingDeleteIntegrationTest < JavaScriptApplicationSystemTestCase + setup do + WebMock.allow_net_connect! + sign_in users(:admin) + + @domain = domains(:shop) + @token = '123456' + + @domain.update!(statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION], + registrant_verification_asked_at: Time.zone.now - 1.day, + registrant_verification_token: @token) + end + + def test_accept_pending_delete + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Accept' + page.driver.browser.switch_to.alert.accept + + assert_text 'Pending was successfully applied.' + end + + def test_accept_pending_delete_no_success + @domain.update!(statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION], + registrant_verification_asked_at: Time.zone.now - 1.day, + registrant_verification_token: nil) + + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Accept' + page.driver.browser.switch_to.alert.accept + + assert_text 'Not success' + end + + def test_reject_panding_delete + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Reject' + page.driver.browser.switch_to.alert.accept + + assert_text 'Pending was successfully removed.' + end + + def test_accept_pending_delete_no_success + @domain.update!(statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION], + registrant_verification_asked_at: Time.zone.now - 1.day, + registrant_verification_token: nil) + + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Reject' + page.driver.browser.switch_to.alert.accept + + assert_text 'Not success' + end +end diff --git a/test/integration/admin_area/pending_update_test.rb b/test/integration/admin_area/pending_update_test.rb new file mode 100644 index 000000000..5d08e104b --- /dev/null +++ b/test/integration/admin_area/pending_update_test.rb @@ -0,0 +1,96 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaPendingUpdateIntegrationTest < JavaScriptApplicationSystemTestCase + + setup do + WebMock.allow_net_connect! + sign_in users(:admin) + + @domain = domains(:hospital) + + @new_registrant = contacts(:jack) + @user = users(:api_bestnames) + @token = '123456' + + @domain.update!(statuses: [DomainStatus::PENDING_UPDATE], + registrant_verification_asked_at: Time.zone.now - 1.day, + registrant_verification_token: @token) + end + + def test_accept_pending_update + pending_json = { new_registrant_id: @new_registrant.id, + new_registrant_name: @new_registrant.name, + new_registrant_email: @new_registrant.email, + current_user_id: @user.id } + + @domain.update(pending_json: pending_json) + @domain.reload + + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Accept' + page.driver.browser.switch_to.alert.accept + + assert_text 'Pending was successfully applied.' + end + + def test_accept_pending_update_no_success + @domain.update!(statuses: [DomainStatus::PENDING_UPDATE], + registrant_verification_asked_at: Time.zone.now - 1.day, + registrant_verification_token: nil) + + pending_json = { new_registrant_id: @new_registrant.id, + new_registrant_name: @new_registrant.name, + new_registrant_email: @new_registrant.email, + current_user_id: @user.id, + } + + @domain.update(pending_json: pending_json) + @domain.reload + + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Accept' + page.driver.browser.switch_to.alert.accept + assert_text 'Not success' + end + + def test_reject_panding_update + pending_json = { new_registrant_id: @new_registrant.id, + new_registrant_name: @new_registrant.name, + new_registrant_email: @new_registrant.email, + current_user_id: @user.id, + } + + @domain.update(pending_json: pending_json) + @domain.reload + + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Reject' + page.driver.browser.switch_to.alert.accept + assert_text 'Pending was successfully removed.' + end + + def test_accept_pending_update_no_success + @domain.update!(statuses: [DomainStatus::PENDING_UPDATE], + registrant_verification_asked_at: Time.zone.now - 1.day, + registrant_verification_token: nil) + + pending_json = { new_registrant_id: @new_registrant.id, + new_registrant_name: @new_registrant.name, + new_registrant_email: @new_registrant.email, + current_user_id: @user.id, + } + + @domain.update(pending_json: pending_json) + @domain.reload + + visit edit_admin_domain_path(id: @domain.id) + + click_on 'Reject' + page.driver.browser.switch_to.alert.accept + assert_text 'Not success' + end +end diff --git a/test/integration/admin_area/registrars_test.rb b/test/integration/admin_area/registrars_test.rb index d73496899..552650791 100644 --- a/test/integration/admin_area/registrars_test.rb +++ b/test/integration/admin_area/registrars_test.rb @@ -17,4 +17,4 @@ class AdminAreaRegistrarsIntegrationTest < ActionDispatch::IntegrationTest assert_equal new_iban, @registrar.iban end -end \ No newline at end of file +end diff --git a/test/integration/admin_area/repp_logs_test.rb b/test/integration/admin_area/repp_logs_test.rb new file mode 100644 index 000000000..6630a6d57 --- /dev/null +++ b/test/integration/admin_area/repp_logs_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaReppLogsIntegrationTest < ApplicationSystemTestCase + setup do + sign_in users(:admin) + end + + def test_repp_logs_page + visit admin_repp_logs_path + assert_text 'REPP log' + end + + def test_show_repp_log_page + visit admin_repp_logs_path + get repp_v1_contacts_path + visit admin_repp_logs_path + + find(:xpath, "//tbody/tr/td/a", match: :first).click + + assert_text 'REPP log' + end +end diff --git a/test/integration/admin_area/reserved_domains_test.rb b/test/integration/admin_area/reserved_domains_test.rb new file mode 100644 index 000000000..c09c3723b --- /dev/null +++ b/test/integration/admin_area/reserved_domains_test.rb @@ -0,0 +1,39 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaReservedDomainsIntegrationTest < JavaScriptApplicationSystemTestCase + + setup do + WebMock.allow_net_connect! + @original_default_language = Setting.default_language + sign_in users(:admin) + + @reserved_domain = reserved_domains(:one) + end + + def test_remove_reserved_domain + visit admin_reserved_domains_path + click_link_or_button 'Delete', match: :first + page.driver.browser.switch_to.alert.accept + + assert_text 'Domain deleted!' + end + + def test_add_invalid_domain + visit admin_reserved_domains_path + click_on 'New reserved domain' + fill_in "Name", with: "@##@$" + click_on 'Save' + + assert_text 'Failed to add domain!' + end + + def test_update_reserved_domain + visit admin_reserved_domains_path + click_link_or_button 'Edit Pw', match: :first + fill_in 'Password', with: '12345678' + click_on 'Save' + + assert_text 'Domain updated!' + end +end diff --git a/test/integration/admin_area/white_ips_test.rb b/test/integration/admin_area/white_ips_test.rb new file mode 100644 index 000000000..499c86f57 --- /dev/null +++ b/test/integration/admin_area/white_ips_test.rb @@ -0,0 +1,94 @@ +require 'test_helper' +require 'application_system_test_case' + +class AdminAreaWhiteIpsIntegrationTest < JavaScriptApplicationSystemTestCase + + setup do + WebMock.allow_net_connect! + sign_in users(:admin) + + @registrar = registrars(:bestnames) + @white_ip = white_ips(:one) + end + + def test_visit_new_whitelisted_ip_page + visit_new_whitelisted_ip_page + end + + def test_create_new_whitelisted_ip + visit_new_whitelisted_ip_page + fill_in 'IPv4', with: "127.0.0.1" + fill_in 'IPv6', with: "::ffff:192.0.2.1" + + find(:css, "#white_ip_interfaces_api").set(true) + find(:css, "#white_ip_interfaces_registrar").set(true) + + click_on 'Save' + + assert_text 'Record created' + end + + def test_failed_to_create_new_whitelisted_ip + visit_new_whitelisted_ip_page + fill_in 'IPv4', with: "asdadadad.asd" + + click_on 'Save' + + assert_text 'Failed to create record' + end + + def test_visit_edit_whitelisted_ip_page + visit_edit_whitelisted_ip_page + end + + def test_update_whitelisted_ip + visit_info_whitelisted_ip_page + click_on 'Edit' + + fill_in 'IPv4', with: "127.0.0.2" + find(:css, "#white_ip_interfaces_api").set(false) + click_on 'Save' + + assert_text 'Record updated' + end + + def test_failed_to_update_whitelisted_ip + visit_info_whitelisted_ip_page + click_on 'Edit' + fill_in 'IPv4', with: "asdadad#" + + click_on 'Save' + + assert_text 'Failed to update record' + end + + def test_visit_info_whitelisted_ip_page + visit_info_whitelisted_ip_page + end + + def test_delete_whitelisted_ip + visit_info_whitelisted_ip_page + click_on 'Delete' + + page.driver.browser.switch_to.alert.accept + + assert_text 'Record deleted' + end + + private + + def visit_new_whitelisted_ip_page + visit new_admin_registrar_white_ip_path(registrar_id: @registrar.id) + assert_text 'New whitelisted IP' + end + + def visit_edit_whitelisted_ip_page + visit edit_admin_registrar_white_ip_path(registrar_id: @registrar.id, id: @white_ip.id) + assert_text 'Edit white IP' + end + + def visit_info_whitelisted_ip_page + visit admin_registrar_white_ip_path(registrar_id: @registrar.id, id: @white_ip.id) + assert_text 'White IP' + end +end diff --git a/test/integration/api/domain_admin_contacts_test.rb b/test/integration/api/domain_admin_contacts_test.rb new file mode 100644 index 000000000..cd5b92865 --- /dev/null +++ b/test/integration/api/domain_admin_contacts_test.rb @@ -0,0 +1,153 @@ +require 'test_helper' + +class APIDomainAdminContactsTest < ApplicationIntegrationTest + setup do + @admin_current = domains(:shop).admin_contacts.find_by(code: 'jane-001') + domain = domains(:airport) + domain.admin_contacts << @admin_current + @admin_new = contacts(:william) + + @admin_new.update(ident: @admin_current.ident, + ident_type: @admin_current.ident_type, + ident_country_code: @admin_current.ident_country_code) + end + + def test_replace_all_admin_contacts_when_ident_data_doesnt_match + @admin_new.update(ident: '777' , + ident_type: 'priv', + ident_country_code: 'LV') + + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_response :bad_request + assert_equal ({ code: 2304, message: 'Admin contacts must be identical', data: {} }), + JSON.parse(response.body, symbolize_names: true) + end + + def test_replace_all_admin_contacts_of_the_current_registrar + assert @admin_new.identical_to?(@admin_current) + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_nil domains(:shop).admin_contacts.find_by(code: @admin_current.code) + assert domains(:shop).admin_contacts.find_by(code: @admin_new.code) + assert domains(:airport).admin_contacts.find_by(code: @admin_new.code) + end + + def test_skip_discarded_domains + domains(:airport).update!(statuses: [DomainStatus::DELETE_CANDIDATE]) + + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert domains(:airport).admin_contacts.find_by(code: @admin_current.code) + end + + def test_return_affected_domains_in_alphabetical_order + domain = domains(:airport) + domain.admin_contacts = [@admin_current] + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_response :ok + assert_equal ({ code: 1000, message: 'Command completed successfully', data: { affected_domains: %w[airport.test shop.test], + skipped_domains: [] }}), + JSON.parse(response.body, symbolize_names: true) + end + + def test_return_skipped_domains_in_alphabetical_order + domains(:shop).update!(statuses: [DomainStatus::DELETE_CANDIDATE]) + domains(:airport).update!(statuses: [DomainStatus::DELETE_CANDIDATE]) + + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_response :ok + assert_equal %w[airport.test shop.test], JSON.parse(response.body, + symbolize_names: true)[:data][:skipped_domains] + end + + def test_keep_other_admin_contacts_intact + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert domains(:airport).admin_contacts.find_by(code: 'john-001') + end + + def test_keep_tech_contacts_intact + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert domains(:airport).tech_contacts.find_by(code: 'william-001') + end + + def test_restrict_contacts_to_the_current_registrar + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: 'william-002' }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_response :not_found + assert_equal ({ code: 2303, message: 'Object does not exist' }), + JSON.parse(response.body, symbolize_names: true) + end + + def test_non_existent_current_contact + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: 'non-existent', + new_contact_id: @admin_new.code}, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + assert_response :not_found + assert_equal ({ code: 2303, message: 'Object does not exist' }), + JSON.parse(response.body, symbolize_names: true) + end + + def test_non_existent_new_contact + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: 'non-existent' }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + assert_response :not_found + assert_equal ({code: 2303, message: 'Object does not exist'}), + JSON.parse(response.body, symbolize_names: true) + end + + def test_disallow_invalid_new_contact + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: 'invalid' }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + assert_response :bad_request + assert_equal ({ code: 2304, message: 'New contact must be valid', data: {} }), + JSON.parse(response.body, symbolize_names: true) + end + + def test_admin_bulk_changed_when_domain_update_prohibited + domains(:shop).update!(statuses: [DomainStatus::SERVER_UPDATE_PROHIBITED]) + domains(:airport).admin_contacts = [@admin_current] + + shop_admin_contact = Contact.find_by(code: 'jane-001') + assert domains(:shop).admin_contacts.include?(shop_admin_contact) + + patch '/repp/v1/domains/admin_contacts', params: { current_contact_id: @admin_current.code, + new_contact_id: @admin_new.code }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_response :ok + assert_equal ({ code: 1000, + message: 'Command completed successfully', + data: { affected_domains: ["airport.test"], + skipped_domains: ["shop.test"] }}), + JSON.parse(response.body, symbolize_names: true) + end + + private + + def http_auth_key + ActionController::HttpAuthentication::Basic.encode_credentials('test_bestnames', 'testtest') + end +end diff --git a/test/integration/api/domain_contacts_test.rb b/test/integration/api/domain_contacts_test.rb index 6704739d1..efd7032b4 100644 --- a/test/integration/api/domain_contacts_test.rb +++ b/test/integration/api/domain_contacts_test.rb @@ -107,6 +107,24 @@ class APIDomainContactsTest < ApplicationIntegrationTest JSON.parse(response.body, symbolize_names: true) end + def test_tech_bulk_changed_when_domain_update_prohibited + domains(:shop).update!(statuses: [DomainStatus::SERVER_UPDATE_PROHIBITED]) + + shop_tech_contact = Contact.find_by(code: 'william-001') + assert domains(:shop).tech_contacts.include?(shop_tech_contact) + + patch '/repp/v1/domains/contacts', params: { current_contact_id: 'william-001', + new_contact_id: 'john-001' }, + headers: { 'HTTP_AUTHORIZATION' => http_auth_key } + + assert_response :ok + assert_equal ({ code: 1000, + message: 'Command completed successfully', + data: { affected_domains: ["airport.test"], + skipped_domains: ["shop.test"] }}), + JSON.parse(response.body, symbolize_names: true) + end + private def http_auth_key diff --git a/test/integration/api/v1/bounces/create_test.rb b/test/integration/api/v1/bounces/create_test.rb index 899b6c5c7..0d3dc65d7 100644 --- a/test/integration/api/v1/bounces/create_test.rb +++ b/test/integration/api/v1/bounces/create_test.rb @@ -2,7 +2,7 @@ require 'test_helper' class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest def setup - @api_key = "Basic #{ENV['api_shared_key']}" + @api_key = "Basic #{ENV['rwhois_bounces_api_shared_key']}" @headers = { "Authorization": "#{@api_key}" } @json_body = { "data": valid_bounce_request }.as_json end diff --git a/test/integration/api/v1/contact_requests_test.rb b/test/integration/api/v1/contact_requests_test.rb new file mode 100644 index 000000000..c9e2ac9bd --- /dev/null +++ b/test/integration/api/v1/contact_requests_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' + +class ApiV1ContactRequestTest < ActionDispatch::IntegrationTest + def setup + @api_key = "Basic #{ENV['rwhois_internal_api_shared_key']}" + @headers = { "Authorization": "#{@api_key}" } + @json_create = { "contact_request": valid_contact_request_create }.as_json + @json_update = { "contact_request": valid_contact_request_update }.as_json + @contact_request = contact_requests(:new) + end + + def test_authorizes_api_request + post api_v1_contact_requests_path, params: @json_create, headers: @headers + assert_response :created + + invalid_headers = { "Authorization": "Basic invalid_api_key" } + post api_v1_contact_requests_path, params: @json_create, headers: invalid_headers + assert_response :unauthorized + end + + def test_saves_new_contact_request + request_body = @json_create.dup + random_mail = "#{rand(10000..99999)}@registry.test" + request_body['contact_request']['email'] = random_mail + + post api_v1_contact_requests_path, params: request_body, headers: @headers + assert_response :created + + contact_request = ContactRequest.last + assert_equal contact_request.email, random_mail + assert ContactRequest::STATUS_NEW, contact_request.status + end + + def test_updates_existing_contact_request + request_body = @json_update.dup + + put api_v1_contact_request_path(@contact_request.id), params: request_body, headers: @headers + assert_response :ok + + @contact_request.reload + assert ContactRequest::STATUS_CONFIRMED, @contact_request.status + end + + def test_not_updates_if_status_error + request_body = @json_update.dup + request_body['contact_request']['status'] = 'some_error_status' + + put api_v1_contact_request_path(@contact_request.id), params: request_body, headers: @headers + assert_response 400 + + @contact_request.reload + assert ContactRequest::STATUS_NEW, @contact_request.status + end + + def valid_contact_request_create + { + "email": "aaa@bbb.com", + "whois_record_id": "1", + "name": "test" + }.as_json + end + + def valid_contact_request_update + { + "status": "#{ContactRequest::STATUS_CONFIRMED}", + }.as_json + end +end diff --git a/test/integration/api/v1/registrant/contacts/list_test.rb b/test/integration/api/v1/registrant/contacts/list_test.rb index 2389019f1..33e8b67eb 100644 --- a/test/integration/api/v1/registrant/contacts/list_test.rb +++ b/test/integration/api/v1/registrant/contacts/list_test.rb @@ -1,5 +1,6 @@ require 'test_helper' require 'auth_token/auth_token_creator' +require 'json' CompanyRegisterClientStub = Struct.new(:any_method) do def representation_rights(citizen_personal_code:, citizen_country_code:) @@ -55,6 +56,42 @@ class RegistrantApiV1ContactListTest < ActionDispatch::IntegrationTest assert_equal '1234', response_json.first[:ident][:code] end + def test_out_of_range_limit + get api_v1_registrant_contacts_path + "?limit=300", as: :json, headers: { 'HTTP_AUTHORIZATION' => auth_token } + response_json = JSON.parse(response.body, symbolize_names: true) + + text_response = JSON.pretty_generate(response_json[:errors][0][:limit][0]) + + assert_equal text_response, '"parameter is out of range"' + end + + def test_negative_offset + get api_v1_registrant_contacts_path + "?offset=-300", as: :json, headers: { 'HTTP_AUTHORIZATION' => auth_token } + response_json = JSON.parse(response.body, symbolize_names: true) + + text_response = JSON.pretty_generate(response_json[:errors][0][:offset][0]) + + assert_equal text_response, '"parameter is out of range"' + end + + def test_show_valid_contact + get api_v1_registrant_contacts_path + "/eb2f2766-b44c-4e14-9f16-32ab1a7cb957", as: :json, headers: { 'HTTP_AUTHORIZATION' => auth_token } + response_json = JSON.parse(response.body, symbolize_names: true) + + text_response = response_json[:name] + + assert_equal @contact[:name], text_response + end + + def test_show_invalid_contact + get api_v1_registrant_contacts_path + "/435", as: :json, headers: { 'HTTP_AUTHORIZATION' => auth_token } + response_json = JSON.parse(response.body, symbolize_names: true) + + text_response = response_json[:errors][0][:base][0] + + assert_equal text_response, 'Contact not found' + end + private def delete_direct_contact diff --git a/test/integration/api/v1/registrant/contacts/update_test.rb b/test/integration/api/v1/registrant/contacts/update_test.rb index 4ddf8b0ff..c1eaa005c 100644 --- a/test/integration/api/v1/registrant/contacts/update_test.rb +++ b/test/integration/api/v1/registrant/contacts/update_test.rb @@ -4,11 +4,12 @@ require 'auth_token/auth_token_creator' class RegistrantApiV1ContactUpdateTest < ActionDispatch::IntegrationTest setup do @contact = contacts(:john) + @contact_org = contacts(:acme_ltd) @original_address_processing = Setting.address_processing @original_fax_enabled_setting = ENV['fax_enabled'] - @user = users(:registrant) + end teardown do @@ -90,6 +91,32 @@ class RegistrantApiV1ContactUpdateTest < ActionDispatch::IntegrationTest @contact.address end + def test_update_address_when_enabled_without_address_params + Setting.address_processing = true + + patch api_v1_registrant_contact_path(@contact.uuid), params: { address: { } }, + as: :json, + headers: { 'HTTP_AUTHORIZATION' => auth_token } + + assert_response :bad_request + @contact.reload + assert_equal Contact::Address.new(nil, nil, nil, nil, nil), + @contact.address + end + + def test_update_address_when_enabled_without_address_params + Setting.address_processing = true + + patch api_v1_registrant_contact_path(@contact.uuid), params: { }, + as: :json, + headers: { 'HTTP_AUTHORIZATION' => auth_token } + + assert_response :bad_request + @contact.reload + assert_equal Contact::Address.new(nil, nil, nil, nil, nil), + @contact.address + end + def test_address_is_optional_when_enabled Setting.address_processing = true @contact.update!(street: 'any', zip: 'any', city: 'any', state: 'any', country_code: 'US') @@ -211,6 +238,21 @@ class RegistrantApiV1ContactUpdateTest < ActionDispatch::IntegrationTest symbolize_names: true) end + def test_org_disclosed_attributes + patch api_v1_registrant_contact_path(@contact_org.uuid), params: { disclosed_attributes: ["some_attr"] }, + as: :json, + headers: { 'HTTP_AUTHORIZATION' => auth_token } + + assert_response :bad_request + + err_msg = "Legal person's data is visible by default and cannot be concealed. Please remove this parameter." + + response_json = JSON.parse(response.body, symbolize_names: true) + response_msg = response_json[:errors][0][:disclosed_attributes][0] + + assert_equal err_msg, response_msg + end + def test_unmanaged_contact_cannot_be_updated assert_equal 'US-1234', @user.registrant_ident @contact.update!(ident: '12345') diff --git a/test/integration/epp/contact/check/base_test.rb b/test/integration/epp/contact/check/base_test.rb index 528d69d86..8f7e3df16 100644 --- a/test/integration/epp/contact/check/base_test.rb +++ b/test/integration/epp/contact/check/base_test.rb @@ -26,7 +26,7 @@ class EppContactCheckBaseTest < EppTestCase response_xml = Nokogiri::XML(response.body) assert_epp_response :completed_successfully - assert_equal 'john-001', response_xml.at_xpath('//contact:id', contact: xml_schema).text + assert_equal "#{@contact.registrar.code}:JOHN-001".upcase, response_xml.at_xpath('//contact:id', contact: xml_schema).text end def test_contact_is_available @@ -52,7 +52,8 @@ class EppContactCheckBaseTest < EppTestCase end def test_contact_is_unavailable - assert_equal 'john-001', @contact.code + @contact.update_columns(code: "#{@contact.registrar.code}:JOHN-001".upcase) + assert @contact.code, "#{@contact.registrar.code}:JOHN-001".upcase request_xml = <<-XML @@ -98,9 +99,61 @@ class EppContactCheckBaseTest < EppTestCase assert_equal 3, response_xml.xpath('//contact:cd', contact: xml_schema).size end + def test_check_contact_with_prefix + @contact.update_columns(code: "#{@contact.registrar.code}:JOHN-001".upcase) + assert @contact.code, "#{@contact.registrar.code}:JOHN-001".upcase + + request_xml = <<-XML + + + + + + BESTNAMES:JOHN-001 + + + + + XML + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_equal "#{@contact.registrar.code}:JOHN-001".upcase, response_xml.at_xpath('//contact:id', contact: xml_schema).text + assert_equal 'in use', response_xml.at_xpath('//contact:reason', contact: xml_schema).text + end + + def test_check_contact_without_prefix + @contact.update_columns(code: "#{@contact.registrar.code}:JOHN-001".upcase) + assert @contact.code, "#{@contact.registrar.code}:JOHN-001".upcase + + request_xml = <<-XML + + + + + + JOHN-001 + + + + + XML + + post epp_check_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_equal "#{@contact.registrar.code}:JOHN-001".upcase, response_xml.at_xpath('//contact:id', contact: xml_schema).text + assert_equal 'in use', response_xml.at_xpath('//contact:reason', contact: xml_schema).text + end + private def xml_schema 'https://epp.tld.ee/schema/contact-ee-1.1.xsd' end -end \ No newline at end of file +end diff --git a/test/integration/epp/contact/info/base_test.rb b/test/integration/epp/contact/info/base_test.rb index 4e4a9190e..3edb6d461 100644 --- a/test/integration/epp/contact/info/base_test.rb +++ b/test/integration/epp/contact/info/base_test.rb @@ -44,6 +44,58 @@ class EppContactInfoBaseTest < EppTestCase contact: xml_schema).text end + def test_get_info_about_contact_with_prefix + @contact.update_columns(code: 'TEST:JOHN-001') + assert @contact.code, 'TEST:JOHN-001' + + request_xml = <<-XML + + + + + + TEST:JOHN-001 + + + + + XML + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_equal 'TEST:JOHN-001', response_xml.at_xpath('//contact:id', contact: xml_schema).text + assert_equal '+555.555', response_xml.at_xpath('//contact:voice', contact: xml_schema).text + end + + def test_get_info_about_contact_without_prefix + @contact.update_columns(code: "#{@contact.registrar.code}:JOHN-001".upcase) + assert @contact.code, "#{@contact.registrar.code}:JOHN-001".upcase + + request_xml = <<-XML + + + + + + JOHN-001 + + + + + XML + + post epp_info_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_epp_response :completed_successfully + assert_equal "#{@contact.registrar.code}:JOHN-001".upcase, response_xml.at_xpath('//contact:id', contact: xml_schema).text + assert_equal '+555.555', response_xml.at_xpath('//contact:voice', contact: xml_schema).text + end + def test_hides_password_and_name_when_current_registrar_is_not_sponsoring non_sponsoring_registrar = registrars(:goodnames) @contact.update!(registrar: non_sponsoring_registrar) diff --git a/test/integration/epp/domain/create/base_test.rb b/test/integration/epp/domain/create/base_test.rb index 9d817524d..35ef38179 100644 --- a/test/integration/epp/domain/create/base_test.rb +++ b/test/integration/epp/domain/create/base_test.rb @@ -2,6 +2,51 @@ require 'test_helper' class EppDomainCreateBaseTest < EppTestCase + def test_illegal_chars_in_dns_key + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + + pub_key = "AwEAAddt2AkLf\n + \n + YGKgiEZB5SmIF8E\n + vrjxNMH6HtxW\rEA4RJ9Ao6LCWheg8" + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + + + + + + 257 + 3 + 8 + #{pub_key} + + + + #{'test' * 2000} + + + + + XML + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + assert_epp_response :parameter_value_syntax_error + end + + def test_not_registers_domain_without_legaldoc now = Time.zone.parse('2010-07-05') travel_to now @@ -31,6 +76,230 @@ class EppDomainCreateBaseTest < EppTestCase assert_epp_response :required_parameter_missing end + def test_create_domain_with_unique_contact + now = Time.zone.parse('2010-07-05') + travel_to now + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{contacts(:jane).code} + #{contacts(:william).code} + + + + + #{'test' * 2000} + + + + + XML + + assert_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + assert_epp_response :completed_successfully + end + + + def test_create_domain_with_array_of_not_unique_admins_and_techs + now = Time.zone.parse('2010-07-05') + travel_to now + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{contact.code} + #{contact.code} + #{contact.code} + #{contact.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + assert_epp_response :parameter_value_policy_error + end + + def test_create_domain_with_array_of_not_unique_admins + now = Time.zone.parse('2010-07-05') + travel_to now + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{contact.code} + #{contact.code} + #{contact.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + assert_epp_response :parameter_value_policy_error + end + + def test_create_domain_with_array_of_not_unique_techs + now = Time.zone.parse('2010-07-05') + travel_to now + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{contact.code} + #{contact.code} + #{contact.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + assert_epp_response :parameter_value_policy_error + end + + def test_create_domain_with_array_of_not_unique_admin_but_tech_another_one + now = Time.zone.parse('2010-07-05') + travel_to now + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + contact_two = contacts(:william) + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{contact.code} + #{contact.code} + #{contact_two.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + assert_epp_response :parameter_value_policy_error + end + + def test_create_domain_with_array_of_not_unique_techs_but_admin_another_one + now = Time.zone.parse('2010-07-05') + travel_to now + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + contact_two = contacts(:william) + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{contact_two.code} + #{contact.code} + #{contact.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + assert_epp_response :parameter_value_policy_error + end + def test_registers_new_domain_with_required_attributes now = Time.zone.parse('2010-07-05') travel_to now diff --git a/test/integration/epp/domain/transfer/request_test.rb b/test/integration/epp/domain/transfer/request_test.rb index 1c3614421..273a9f490 100644 --- a/test/integration/epp/domain/transfer/request_test.rb +++ b/test/integration/epp/domain/transfer/request_test.rb @@ -3,6 +3,7 @@ require 'test_helper' class EppDomainTransferRequestTest < EppTestCase def setup @domain = domains(:shop) + @contact = contacts(:jane) @new_registrar = registrars(:goodnames) @original_transfer_wait_time = Setting.transfer_wait_time Setting.transfer_wait_time = 0 @@ -12,6 +13,95 @@ class EppDomainTransferRequestTest < EppTestCase Setting.transfer_wait_time = @original_transfer_wait_time end + def test_transfer_domain_with_contacts_if_registrant_and_tech_are_shared + @domain.tech_domain_contacts[0].update!(contact_id: @domain.registrant.id) + + @domain.tech_domain_contacts[1].delete + @domain.reload + + post epp_transfer_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_goodnames' } + + assert_epp_response :completed_successfully + + @domain.reload + + tech = Contact.find_by(id: @domain.tech_domain_contacts[0].contact_id) + + assert_equal @domain.contacts.where(original_id: @domain.registrant.original_id).count, 1 + assert_equal tech.registrar_id, @domain.registrar.id + end + + def test_transfer_domain_with_contacts_if_registrant_and_admin_are_shared + @domain.admin_domain_contacts[0].update!(contact_id: @domain.registrant.id) + @domain.tech_domain_contacts[0].update!(contact_id: @contact.id) + + @domain.tech_domain_contacts[1].delete + @domain.reload + + post epp_transfer_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_goodnames' } + + assert_epp_response :completed_successfully + + @domain.reload + + admin = Contact.find_by(id: @domain.admin_domain_contacts[0].contact_id) + + assert_equal @domain.contacts.where(original_id: @domain.registrant.original_id).count, 1 + assert_equal admin.registrar_id, @domain.registrar.id + end + + def test_transfer_domain_with_contacts_if_admin_and_tech_are_shared + @domain.admin_domain_contacts[0].update!(contact_id: @contact.id) + @domain.tech_domain_contacts[0].update!(contact_id: @contact.id) + + @domain.tech_domain_contacts[1].delete + @domain.reload + + post epp_transfer_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_goodnames' } + + assert_epp_response :completed_successfully + + @domain.reload + + admin = Contact.find_by(id: @domain.admin_domain_contacts[0].contact_id) + tech = Contact.find_by(id: @domain.tech_domain_contacts[0].contact_id) + + result_hash = @domain.contacts.pluck(:original_id).group_by(&:itself).transform_values(&:count) + assert result_hash[admin.original_id], 2 + + assert_equal admin.registrar_id, @domain.registrar.id + assert_equal tech.registrar_id, @domain.registrar.id + end + + def test_transfer_domain_with_contacts_if_admin_and_tech_and_registrant_are_shared + @domain.tech_domain_contacts[0].update!(contact_id: @domain.registrant.id) + @domain.admin_domain_contacts[0].update!(contact_id: @domain.registrant.id) + + @domain.tech_domain_contacts[1].delete + @domain.reload + + post epp_transfer_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_goodnames' } + + assert_epp_response :completed_successfully + + @domain.reload + + admin = Contact.find_by(id: @domain.admin_domain_contacts[0].contact_id) + tech = Contact.find_by(id: @domain.tech_domain_contacts[0].contact_id) + + assert_equal @domain.contacts.where(original_id: @domain.registrant.original_id).count, 2 + + result_hash = @domain.contacts.pluck(:original_id).group_by(&:itself).transform_values(&:count) + assert result_hash[@domain.registrant.original_id], 2 + + assert_equal admin.registrar_id, @domain.registrar.id + assert_equal tech.registrar_id, @domain.registrar.id + end + def test_transfers_domain_at_once post epp_transfer_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_goodnames' } diff --git a/test/jobs/domain_update_confirm_job_test.rb b/test/jobs/domain_update_confirm_job_test.rb index ded0d3d8a..d2d3a3252 100644 --- a/test/jobs/domain_update_confirm_job_test.rb +++ b/test/jobs/domain_update_confirm_job_test.rb @@ -1,5 +1,4 @@ require "test_helper" - class DomainUpdateConfirmJobTest < ActiveSupport::TestCase def setup super @@ -19,6 +18,22 @@ class DomainUpdateConfirmJobTest < ActiveSupport::TestCase super end + def test_registrant_locked_domain + refute @domain.locked_by_registrant? + @domain.apply_registry_lock + assert @domain.locked_by_registrant? + assert_equal(@domain.registrar.notifications.last.text, "Domain #{@domain.name} has been locked by registrant") + end + + def test_registrant_unlocked_domain + refute @domain.locked_by_registrant? + @domain.apply_registry_lock + assert @domain.locked_by_registrant? + @domain.remove_registry_lock + refute @domain.locked_by_registrant? + assert_equal(@domain.registrar.notifications.last.text, "Domain #{@domain.name} has been unlocked by registrant") + end + def test_rejected_registrant_verification_notifies_registrar DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) diff --git a/test/models/domain/releasable/auctionable_test.rb b/test/models/domain/releasable/auctionable_test.rb index de3ac0ff6..d24f46913 100644 --- a/test/models/domain/releasable/auctionable_test.rb +++ b/test/models/domain/releasable/auctionable_test.rb @@ -25,6 +25,7 @@ class DomainReleasableAuctionableTest < ActiveSupport::TestCase def test_skips_auction_when_domains_is_blocked assert_equal 'shop.test', @domain.name blocked_domains(:one).update!(name: 'shop.test') + @domain.save!(validate: false) @domain.release @@ -34,6 +35,7 @@ class DomainReleasableAuctionableTest < ActiveSupport::TestCase def test_skips_auction_when_domains_is_reserved assert_equal 'shop.test', @domain.name reserved_domains(:one).update!(name: 'shop.test') + @domain.save!(validate: false) @domain.release @@ -58,6 +60,24 @@ class DomainReleasableAuctionableTest < ActiveSupport::TestCase end end + def test_updates_whois_server + @domain.update!(delete_date: '2010-07-04') + travel_to Time.zone.parse('2010-07-05') + old_whois = @domain.whois_record + + Domain.release_domains + + assert_raises ActiveRecord::RecordNotFound do + old_whois.reload + end + + whois_record = Whois::Record.find_by(name: @domain.name) + json = { "name"=>@domain.name, + "status"=>["AtAuction"], + "disclaimer"=> Setting.registry_whois_disclaimer } + assert_equal whois_record.json, json + end + def test_notifies_registrar @domain.update!(delete_date: '2010-07-04') travel_to Time.zone.parse('2010-07-05') diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb index ae12f4a1e..15ab8b0c2 100644 --- a/test/models/domain_test.rb +++ b/test/models/domain_test.rb @@ -69,6 +69,12 @@ class DomainTest < ActiveSupport::TestCase domain.name = 'xn--mnchen-3ya.test' assert domain.valid? + + domain.name = '####' + assert domain.invalid? + + domain.name = 'https://example.test' + assert domain.invalid? end def test_invalid_when_name_is_already_taken diff --git a/test/models/white_ip_test.rb b/test/models/white_ip_test.rb index 607887730..ba5abe42f 100644 --- a/test/models/white_ip_test.rb +++ b/test/models/white_ip_test.rb @@ -38,6 +38,20 @@ class WhiteIpTest < ActiveSupport::TestCase assert white_ip.valid? end + def test_validates_include_empty_ipv4 + white_ip = WhiteIp.new + + white_ip.ipv4 = nil + white_ip.ipv6 = '001:0db8:85a3:0000:0000:8a2e:0370:7334' + white_ip.registrar = registrars(:bestnames) + + assert_nothing_raised { white_ip.save } + assert white_ip.valid? + + assert WhiteIp.include_ip?(white_ip.ipv6) + assert_not WhiteIp.include_ip?('192.168.1.1') + end + private def valid_white_ip diff --git a/test/system/admin_area/bank_statement_test.rb b/test/system/admin_area/bank_statement_test.rb index 6de21b1c3..8630049dc 100644 --- a/test/system/admin_area/bank_statement_test.rb +++ b/test/system/admin_area/bank_statement_test.rb @@ -4,6 +4,35 @@ class AdminAreaBankStatementTest < ApplicationSystemTestCase setup do sign_in users(:admin) travel_to Time.zone.parse('2010-07-05 00:30:00') + + @invoice = invoices(:one) + end + + def test_update_bank_statement + visit admin_bank_statement_path(id: @invoice.id) + + click_link_or_button 'Add' + + fill_in 'Description', with: 'Invoice with id 123' + fill_in 'Reference number', with: '1232' + fill_in 'Sum', with: '500' + fill_in 'Paid at', with: Time.zone.today.to_s + + click_link_or_button 'Save' + assert_text 'Bank transaction' + + click_link_or_button 'Edit' + fill_in 'Description', with: 'Invoice with id 123' + click_link_or_button 'Save' + + assert_text 'Record updated' + end + + def test_bind_bank + visit admin_bank_statement_path(id: @invoice.id) + click_link_or_button 'Bind invoices' + + assert_text 'No invoices were binded' end def test_can_create_statement_manually diff --git a/test/system/admin_area/contacts_test.rb b/test/system/admin_area/contacts_test.rb index d98882dff..19b15c8a5 100644 --- a/test/system/admin_area/contacts_test.rb +++ b/test/system/admin_area/contacts_test.rb @@ -8,6 +8,17 @@ class AdminContactsTest < ApplicationSystemTestCase sign_in users(:admin) end + def test_update_contact + visit admin_contact_path(id: @contact.id) + assert_text "#{@contact.name}" + + click_on 'Edit statuses' + assert_text "Edit: #{@contact.name}" + + click_on 'Save' + assert_text 'Contact updated' + end + def test_display_list visit admin_contacts_path diff --git a/test/system/admin_area/domains/csv_test.rb b/test/system/admin_area/domains/csv_test.rb new file mode 100644 index 000000000..7d4b44124 --- /dev/null +++ b/test/system/admin_area/domains/csv_test.rb @@ -0,0 +1,18 @@ +require 'application_system_test_case' + +class AdminAreaCsvTest < ApplicationSystemTestCase + setup do + sign_in users(:admin) + end + + def test_downloads_domain_list_as_csv + search_params = {"valid_to_lteq"=>nil} + expected_csv = Domain.includes(:registrar, :registrant).search(search_params).result.to_csv + + travel_to Time.zone.parse('2010-07-05 10:30') + visit admin_domains_url + click_link('CSV') + assert_equal "attachment; filename=\"domains.csv\"; filename*=UTF-8''domains.csv", response_headers['Content-Disposition'] + assert_equal expected_csv, page.body + end +end diff --git a/test/system/admin_area/domains/legal_doc_test.rb b/test/system/admin_area/domains/legal_doc_test.rb index 00cc7cc3a..48eedcf4f 100644 --- a/test/system/admin_area/domains/legal_doc_test.rb +++ b/test/system/admin_area/domains/legal_doc_test.rb @@ -15,7 +15,7 @@ class AdminAreaDomainsLegalDocTest < ApplicationSystemTestCase def test_absent_doc_downloading_without_errors visit admin_domain_url(@domain) assert_nothing_raised do - click_on "#{@document.created_at}" + click_on "#{@document.created_at}", match: :first end end end diff --git a/test/system/admin_area/invoices_test.rb b/test/system/admin_area/invoices_test.rb index 814f95d4a..40a50e1c7 100644 --- a/test/system/admin_area/invoices_test.rb +++ b/test/system/admin_area/invoices_test.rb @@ -40,4 +40,4 @@ class AdminAreaInvoicesTest < ApplicationSystemTestCase assert_current_path admin_invoice_path(@invoice) assert_text 'Invoice has been sent' end -end \ No newline at end of file +end diff --git a/test/system/admin_area/protected_area_test.rb b/test/system/admin_area/protected_area_test.rb index f3375776a..fa413291b 100644 --- a/test/system/admin_area/protected_area_test.rb +++ b/test/system/admin_area/protected_area_test.rb @@ -19,4 +19,4 @@ class AdminAreaProtectedAreaTest < ApplicationSystemTestCase assert_text 'You are already signed in' assert_current_path admin_domains_path end -end \ No newline at end of file +end diff --git a/test/system/registrar_area/account_activities_test.rb b/test/system/registrar_area/account_activities_test.rb new file mode 100644 index 000000000..1944dc2fb --- /dev/null +++ b/test/system/registrar_area/account_activities_test.rb @@ -0,0 +1,28 @@ +require 'application_system_test_case' + +class RegistrarAccountActivitiesTest < ApplicationSystemTestCase + setup do + @registrar = registrars(:bestnames) + sign_in users(:api_bestnames) + end + + def test_show_account_activity_page + account_activities(:one).update(sum: "123.00") + visit registrar_account_activities_path + assert_text 'Account activity' + end + + def test_download_account_activity + now = Time.zone.parse('2010-07-05 08:00') + travel_to now + account_activities(:one).update(sum: "123.00") + + get registrar_account_activities_path(format: :csv) + + assert_response :ok + assert_equal "text/csv", response.headers['Content-Type'] + assert_equal %(attachment; filename="account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"; filename*=UTF-8''account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv), + response.headers['Content-Disposition'] + assert_not_empty response.body + end +end \ No newline at end of file diff --git a/test/system/registrar_area/bulk_change/admin_contact_test.rb b/test/system/registrar_area/bulk_change/admin_contact_test.rb new file mode 100644 index 000000000..8847812cb --- /dev/null +++ b/test/system/registrar_area/bulk_change/admin_contact_test.rb @@ -0,0 +1,49 @@ +require 'application_system_test_case' + +class RegistrarAreaAdminContactBulkChangeTest < ApplicationSystemTestCase + setup do + sign_in users(:api_bestnames) + end + + def test_replace_domain_contacts_of_current_registrar + request_stub = stub_request(:patch, /domains\/admin_contacts/) + .with(body: { current_contact_id: 'william-001', new_contact_id: 'john-001' }, + basic_auth: ['test_bestnames', 'testtest']) + .to_return(body: { data: { affected_domains: %w[foo.test bar.test], + skipped_domains: %w[baz.test qux.test] } }.to_json, + status: 200) + + visit registrar_domains_url + click_link 'Bulk change' + click_link 'Admin contact' + + find('.current_admin_contact').fill_in 'Current contact ID', with: 'william-001' + find('.new_admin_contact').fill_in 'New contact ID', with: 'john-001' + click_on 'Replace admin contacts' + + assert_requested request_stub + assert_current_path registrar_domains_path + assert_text 'Admin contacts have been successfully replaced' + assert_text 'Affected domains: foo.test, bar.test' + assert_text 'Skipped domains: baz.test, qux.test' + end + + def test_fails_gracefully + stub_request(:patch, /domains\/admin_contacts/) + .to_return(status: 400, + body: { message: 'epic fail' }.to_json, + headers: { 'Content-type' => Mime[:json] }) + + visit registrar_domains_url + click_link 'Bulk change' + click_link 'Admin contact' + + find('.current_admin_contact').fill_in 'Current contact ID', with: 'william-001' + find('.new_admin_contact').fill_in 'New contact ID', with: 'john-001' + click_on 'Replace admin contacts' + + assert_text 'epic fail' + assert_field 'Current contact ID', with: 'william-001' + assert_field 'New contact ID', with: 'john-001' + end +end diff --git a/test/system/registrar_area/bulk_change/tech_contact_test.rb b/test/system/registrar_area/bulk_change/tech_contact_test.rb index e08457f60..055ec25ca 100644 --- a/test/system/registrar_area/bulk_change/tech_contact_test.rb +++ b/test/system/registrar_area/bulk_change/tech_contact_test.rb @@ -16,8 +16,8 @@ class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase visit registrar_domains_url click_link 'Bulk change' - fill_in 'Current contact ID', with: 'william-001' - fill_in 'New contact ID', with: 'john-001' + find('.current_tech_contact').fill_in 'Current contact ID', with: 'william-001' + find('.new_tech_contact').fill_in 'New contact ID', with: 'john-001' click_on 'Replace technical contacts' assert_requested request_stub @@ -36,8 +36,8 @@ class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase visit registrar_domains_url click_link 'Bulk change' - fill_in 'Current contact ID', with: 'william-001' - fill_in 'New contact ID', with: 'john-001' + find('.current_tech_contact').fill_in 'Current contact ID', with: 'william-001' + find('.new_tech_contact').fill_in 'New contact ID', with: 'john-001' click_on 'Replace technical contacts' assert_text 'epic fail'