Merge remote-tracking branch 'origin/add-registrant-api-token-log' into add-role-filter-to-registrant-api

This commit is contained in:
Karl Erik Õunapuu 2021-02-23 13:48:05 +02:00
commit f60bf4013c
No known key found for this signature in database
GPG key ID: C9DD647298A34764
60 changed files with 1153 additions and 178 deletions

View file

@ -16,8 +16,10 @@ plugins:
enabled: true enabled: true
config: config:
languages: languages:
- ruby ruby:
- javascript mass_threshold: 100
javascript:
mass_threshold: 100
eslint: eslint:
enabled: true enabled: true
channel: eslint-5 channel: eslint-5

View file

@ -1,3 +1,26 @@
22.02.2021
* serverDeleteProhibited prohibts delete action [#1849](https://github.com/internetee/registry/issues/1849)
19.02.2021
* Update prohibited staatus is kept after renew [#1843](https://github.com/internetee/registry/issues/1843)
* Fixed clientHold and serverManualInzone status conflict issue [#1845](https://github.com/internetee/registry/issues/1845)
* Replacing registrant object with another that has the same ident data set does not require registrant verification [#1852](https://github.com/internetee/registry/issues/1852)
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 02.02.2021
* Fixed updateProhibited status not affecting bulk tech contact change operation [#1820](https://github.com/internetee/registry/pull/1820) * Fixed updateProhibited status not affecting bulk tech contact change operation [#1820](https://github.com/internetee/registry/pull/1820)

View file

@ -92,3 +92,5 @@ group :test do
gem 'webdrivers' gem 'webdrivers'
gem 'webmock' gem 'webmock'
end end
gem 'aws-sdk-sesv2', '~> 1.16'

View file

@ -148,6 +148,18 @@ GEM
attr_required (1.0.1) attr_required (1.0.1)
autoprefixer-rails (10.0.0.2) autoprefixer-rails (10.0.0.2)
execjs 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) bcrypt (3.1.16)
bindata (2.4.8) bindata (2.4.8)
bootsnap (1.4.8) bootsnap (1.4.8)
@ -226,6 +238,7 @@ GEM
i18n_data (0.10.0) i18n_data (0.10.0)
isikukood (0.1.2) isikukood (0.1.2)
iso8601 (0.12.1) iso8601 (0.12.1)
jmespath (1.4.0)
jquery-rails (4.4.0) jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
@ -484,6 +497,7 @@ DEPENDENCIES
active_interaction (~> 3.8) active_interaction (~> 3.8)
activerecord-import activerecord-import
airbrake airbrake
aws-sdk-sesv2 (~> 1.16)
bootsnap (>= 1.1.0) bootsnap (>= 1.1.0)
bootstrap-sass (~> 3.4) bootstrap-sass (~> 3.4)
cancancan cancancan

View file

@ -11,7 +11,7 @@ module Api
end end
def authenticate_shared_key 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 head(:unauthorized) unless api_key == request.authorization
end end

View file

@ -1,7 +1,7 @@
module Api module Api
module V1 module V1
class BouncesController < BaseController class BouncesController < BaseController
before_action :authenticate_shared_key before_action :validate_shared_key_integrity
# POST api/v1/bounces/ # POST api/v1/bounces/
def create def create
@ -20,6 +20,13 @@ module Api
params.require(:data) params.require(:data)
end 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 end
end end

View file

@ -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

View file

@ -19,6 +19,8 @@ module Api
token = create_token(user) token = create_token(user)
if token if token
ToStdout.msg("Bearer for #{eid_params[:first_name]} #{eid_params[:last_name]} " \
"(#{eid_params[:ident]}) - '#{token[:access_token]}'")
render json: token render json: token
else else
render json: { errors: [{ base: ['Cannot create generate session token'] }] } render json: { errors: [{ base: ['Cannot create generate session token'] }] }

View file

@ -14,7 +14,7 @@ module Epp
authorize! :check, Epp::Contact authorize! :check, Epp::Contact
ids = params[:parsed_frame].css('id').map(&:text) 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' render_epp_response '/epp/contacts/check'
end end
@ -93,7 +93,11 @@ module Epp
def find_contact def find_contact
code = params[:parsed_frame].css('id').text.strip.upcase 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 end
# #

View file

@ -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

View file

@ -26,6 +26,84 @@ class Registrar
private 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? def ready_to_renew?
domain_ids_for_bulk_renew.present? && params[:renew].present? domain_ids_for_bulk_renew.present? && params[:renew].present?
end end

View file

@ -25,32 +25,7 @@ class Registrar
current_registrar_user.plain_text_password) current_registrar_user.plain_text_password)
if Rails.env.test? response = do_request(request, uri)
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) parsed_response = JSON.parse(response.body, symbolize_names: true)

View file

@ -18,32 +18,7 @@ class Registrar
request.basic_auth(current_registrar_user.username, request.basic_auth(current_registrar_user.username,
current_registrar_user.plain_text_password) current_registrar_user.plain_text_password)
if Rails.env.test? response = do_request(request, uri)
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) parsed_response = JSON.parse(response.body, symbolize_names: true)

View file

@ -1,62 +1,19 @@
class Registrar class Registrar
class TechContactsController < BulkChangeController class TechContactsController < BulkChangeController
BASE_URL = URI.parse("#{ENV['repp_url']}domains/contacts").freeze
ACTIVE_TAB = :technical_contact
def update def update
authorize! :manage, :repp 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) process_response(response: response,
request.set_form_data(current_contact_id: params[:current_contact_id], start_notice: start_notice,
new_contact_id: params[:new_contact_id]) active_tab: ACTIVE_TAB)
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
end end
end end
end end

View file

@ -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

View file

@ -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

View file

@ -1,23 +1,9 @@
module Repp module Repp
module V1 module V1
module Domains module Domains
class ContactsController < BaseController class ContactsController < BaseContactsController
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 def update
@epp_errors ||= [] super
@epp_errors << { code: 2304, msg: 'New contact must be valid' } if @new_contact.invalid?
if @new_contact == @current_contact if @new_contact == @current_contact
@epp_errors << { code: 2304, msg: 'New contact must be different from current' } @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 } @response = { affected_domains: affected, skipped_domains: skipped }
render_success(data: @response) render_success(data: @response)
end 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
end end

View file

@ -19,6 +19,11 @@ module Actions
return return
end end
if contact.delete_prohibited?
contact.errors.add(:statuses, :delete_prohibited)
return
end
commit commit
end end

View file

@ -1,2 +1,26 @@
class AdminDomainContact < DomainContact 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 end

View file

@ -1,5 +1,6 @@
class BouncedMailAddress < ApplicationRecord class BouncedMailAddress < ApplicationRecord
validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true
after_destroy :destroy_aws_suppression
def bounce_reason def bounce_reason
"#{action} (#{status} #{diagnostic})" "#{action} (#{status} #{diagnostic})"
@ -25,4 +26,20 @@ class BouncedMailAddress < ApplicationRecord
diagnostic: bounced_record['diagnosticCode'], diagnostic: bounced_record['diagnosticCode'],
} }
end 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 end

View file

@ -11,6 +11,13 @@ module Concerns::Contact::Identical
ident_country_code ident_country_code
org_name org_name
] ]
IDENTICAL_ATTRIBUTES = %w[
ident
ident_type
ident_country_code
].freeze
private_constant :IDENTIFIABLE_ATTRIBUTES private_constant :IDENTIFIABLE_ATTRIBUTES
def identical(registrar) def identical(registrar)
@ -20,6 +27,12 @@ module Concerns::Contact::Identical
.where.not(id: id).take .where.not(id: id).take
end end
def identical_to?(contact)
IDENTICAL_ATTRIBUTES.all? do |attribute|
attributes[attribute] == contact.attributes[attribute]
end
end
private private
def identifiable_hash def identifiable_hash

View file

@ -1,6 +1,12 @@
module Concerns::Domain::Deletable module Concerns::Domain::Deletable
extend ActiveSupport::Concern extend ActiveSupport::Concern
DELETE_STATUSES = [
DomainStatus::PENDING_DELETE_CONFIRMATION,
DomainStatus::PENDING_DELETE,
DomainStatus::FORCE_DELETE,
].freeze
private private
def delete_later def delete_later

View file

@ -11,6 +11,11 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength
lambda { lambda {
where("(force_delete_data->>'contact_notification_sent_date') is null") where("(force_delete_data->>'contact_notification_sent_date') is null")
} }
HOLD_STATUSES = [
DomainStatus::SERVER_HOLD,
DomainStatus::CLIENT_HOLD,
].freeze
end end
class_methods do class_methods do
@ -19,6 +24,10 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength
end end
end end
def hold_status?
HOLD_STATUSES.any? { |status| statuses.include? status }
end
def notification_template(explicit: nil) def notification_template(explicit: nil)
reason = explicit&.downcase reason = explicit&.downcase
return reason if %w[invalid_email invalid_phone].include?(reason) return reason if %w[invalid_email invalid_phone].include?(reason)

View file

@ -12,6 +12,7 @@ module Concerns
statuses << DomainStatus::SERVER_DELETE_PROHIBITED statuses << DomainStatus::SERVER_DELETE_PROHIBITED
statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED
self.locked_by_registrant_at = Time.zone.now self.locked_by_registrant_at = Time.zone.now
alert_registrar_lock_changes!(lock: true)
save! save!
end end
@ -42,10 +43,21 @@ module Concerns
statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED)
statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED)
self.locked_by_registrant_at = nil self.locked_by_registrant_at = nil
alert_registrar_lock_changes!(lock: false)
save! save!
end end
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 end
end end

View file

@ -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

View file

@ -107,9 +107,9 @@ class Domain < ApplicationRecord
validate :status_is_consistant validate :status_is_consistant
def status_is_consistant def status_is_consistant
has_error = (statuses.include?(DomainStatus::SERVER_HOLD) && statuses.include?(DomainStatus::SERVER_MANUAL_INZONE)) has_error = (hold_status? && statuses.include?(DomainStatus::SERVER_MANUAL_INZONE))
unless has_error unless has_error
if (statuses & [DomainStatus::PENDING_DELETE_CONFIRMATION, DomainStatus::PENDING_DELETE, DomainStatus::FORCE_DELETE]).any? if (statuses & DELETE_STATUSES).any?
has_error = statuses.include? DomainStatus::SERVER_DELETE_PROHIBITED has_error = statuses.include? DomainStatus::SERVER_DELETE_PROHIBITED
end end
end end

View file

@ -42,17 +42,12 @@ class Epp::Contact < Contact
) )
end end
def check_availability(codes) def check_availability(codes, reg:)
codes = [codes] if codes.is_a?(String) codes = [codes] if codes.is_a?(String)
res = [] res = []
codes.each do |x| codes.map { |c| c.include?(':') ? c : "#{reg}:#{c}" }.map { |c| c.strip.upcase }.each do |x|
contact = find_by_epp_code(x) c = find_by_epp_code(x)
if contact res << (c ? { code: c.code, avail: 0, reason: 'in use' } : { code: x, avail: 1 })
res << { code: contact.code, avail: 0, reason: 'in use' }
else
res << { code: x, avail: 1 }
end
end end
res res
@ -87,8 +82,11 @@ class Epp::Contact < Contact
'2302' => [ # Object exists '2302' => [ # Object exists
[:code, :epp_id_taken] [:code, :epp_id_taken]
], ],
'2304' => [ # Status prohibits operation
[:statuses, :delete_prohibited],
],
'2305' => [ # Association exists '2305' => [ # Association exists
[:domains, :exist] [:domains, :exist],
] ]
} }
end end

View file

@ -495,7 +495,7 @@ class Epp::Domain < Domain
registrant_verification_needed = false registrant_verification_needed = false
# registrant block may not be present, so we need this to rule out false positives # registrant block may not be present, so we need this to rule out false positives
if frame.css('registrant').text.present? if frame.css('registrant').text.present?
registrant_verification_needed = (registrant.code != frame.css('registrant').text) registrant_verification_needed = verification_needed?(code: frame.css('registrant').text)
end end
if registrant_verification_needed && disputed? if registrant_verification_needed && disputed?
@ -612,7 +612,6 @@ class Epp::Domain < Domain
statuses.delete(DomainStatus::SERVER_HOLD) statuses.delete(DomainStatus::SERVER_HOLD)
statuses.delete(DomainStatus::EXPIRED) statuses.delete(DomainStatus::EXPIRED)
statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED)
cancel_pending_delete cancel_pending_delete
save save
@ -798,4 +797,13 @@ class Epp::Domain < Domain
result result
end end
end end
private
def verification_needed?(code:)
new_registrant = Registrant.find_by(code: code)
return false if new_registrant.try(:identical_to?, registrant)
registrant.code != code
end
end end

View file

@ -25,6 +25,10 @@ class Notification < ApplicationRecord
'' ''
end end
def registry_lock?
text.include?('has been locked') || text.include?('has been unlocked')
end
private private
def set_defaults def set_defaults

View file

@ -4,8 +4,13 @@ class WhiteIp < ApplicationRecord
validate :valid_ipv4? validate :valid_ipv4?
validate :valid_ipv6? validate :valid_ipv6?
validate :validate_ipv4_and_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 def validate_ipv4_and_ipv6
return if ipv4.present? || ipv6.present? return if ipv4.present? || ipv6.present?
errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present)) errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present))
@ -50,10 +55,10 @@ class WhiteIp < ApplicationRecord
def ids_including(ip) def ids_including(ip)
ipv4 = ipv6 = [] ipv4 = ipv6 = []
if check_ip4(ip).present? 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 end
if check_ip6(ip).present? 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 end
(ipv4 + ipv6).pluck(:id).flatten.uniq (ipv4 + ipv6).pluck(:id).flatten.uniq
end end

View file

@ -97,7 +97,9 @@ class WhoisRecord < ApplicationRecord
end end
def destroy_whois_record def destroy_whois_record
Whois::Record.without_auctions.where(name: name).delete_all return if Auction.find_by(domain: name).present?
Whois::Record.where(name: name).delete_all
end end
private private

View file

@ -15,13 +15,23 @@ xml.epp_head do
end if @object end if @object
end end
if @notification.action&.contact 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', render(partial: 'epp/poll/action',
locals: { locals: {
builder: xml, builder: xml,
action: @notification.action action: @notification.action,
}) })
end end
end
render('epp/shared/trID', builder: xml) render('epp/shared/trID', builder: xml)
end end

View file

@ -0,0 +1,65 @@
<%= form_tag registrar_admin_contacts_path, method: :patch, class: 'form-horizontal' do %>
<% if @error %>
<div class="alert alert-danger">
<%= @error %>
</div>
<% end %>
<div class="form-group">
<div class="row">
<div class="col-md-6 control-label">
<p><%= t '.comment' %></p>
</div>
</div>
<div class="col-md-2 control-label">
<%= label_tag :current_contact_id, t('.current_contact_id') %>
</div>
<div class="col-md-4 current_admin_contact">
<%= text_field_tag :current_contact_id, params[:current_contact_id],
list: :contacts,
required: true,
autofocus: true,
class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="col-md-2 control-label">
<%= label_tag :new_contact_id, t('.new_contact_id') %>
</div>
<div class="col-md-4 new_admin_contact">
<%= text_field_tag :new_contact_id, params[:new_contact_id],
list: :contacts,
required: true,
class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="col-md-4 col-md-offset-2 text-right">
<button class="btn btn-warning">
<%= t '.submit_btn' %>
</button>
</div>
</div>
<div class="form-group">
<div class="col-md-6">
<a class="btn btn-default btn-xs" role="button" data-toggle="collapse"
href="#bulk_change_tech_contact_help"><%= t '.help_btn' %></a>
<div class="collapse" id="bulk_change_tech_contact_help">
<div class="well">
<%= t '.help' %>
</div>
</div>
</div>
</div>
<% end %>
<datalist id="contacts">
<% available_contacts.each do |data| %>
<option value="<%= data.second %>"><%= data.first %></option>
<% end %>
</datalist>

View file

@ -10,7 +10,7 @@
<%= label_tag :current_contact_id, t('.current_contact_id') %> <%= label_tag :current_contact_id, t('.current_contact_id') %>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 current_tech_contact">
<%= text_field_tag :current_contact_id, params[:current_contact_id], <%= text_field_tag :current_contact_id, params[:current_contact_id],
list: :contacts, list: :contacts,
required: true, required: true,
@ -24,7 +24,7 @@
<%= label_tag :new_contact_id, t('.new_contact_id') %> <%= label_tag :new_contact_id, t('.new_contact_id') %>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 new_tech_contact">
<%= text_field_tag :new_contact_id, params[:new_contact_id], <%= text_field_tag :new_contact_id, params[:new_contact_id],
list: :contacts, list: :contacts,
required: true, required: true,

View file

@ -12,6 +12,10 @@
<a href="#technical_contact" data-toggle="tab"><%= t '.technical_contact' %></a> <a href="#technical_contact" data-toggle="tab"><%= t '.technical_contact' %></a>
</li> </li>
<li class="<%= 'active' if active_tab == :admin_contact %>">
<a href="#admin_contact" data-toggle="tab"><%= t '.admin_contact' %></a>
</li>
<li class="<%= 'active' if active_tab == :nameserver %>"> <li class="<%= 'active' if active_tab == :nameserver %>">
<a href="#nameserver" data-toggle="tab"><%= t '.nameserver' %></a> <a href="#nameserver" data-toggle="tab"><%= t '.nameserver' %></a>
</li> </li>
@ -31,6 +35,11 @@
<%= render 'tech_contact_form', available_contacts: available_contacts %> <%= render 'tech_contact_form', available_contacts: available_contacts %>
</div> </div>
<div class="tab-pane<%= ' active' if active_tab == :admin_contact %>"
id="admin_contact">
<%= render 'admin_contact_form', available_contacts: available_contacts %>
</div>
<div class="tab-pane<%= ' active' if active_tab == :nameserver %>" id="nameserver"> <div class="tab-pane<%= ' active' if active_tab == :nameserver %>" id="nameserver">
<%= render 'nameserver_form' %> <%= render 'nameserver_form' %>
</div> </div>

View file

@ -87,8 +87,11 @@ sk_digi_doc_service_name: 'Testimine'
registrant_api_base_url: registrant_api_base_url:
registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas
# Bounces API # Shared key for REST-WHOIS Bounces API incl. CERT
api_shared_key: testkey 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 # Base URL (inc. https://) of REST registrant portal
# Leave blank to use internal registrant portal # Leave blank to use internal registrant portal

View file

@ -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'])
)

View file

@ -25,8 +25,10 @@ en:
email_regex_check_error: Invalid format email_regex_check_error: Invalid format
domains: domains:
exist: 'Object association prohibits operation' exist: 'Object association prohibits operation'
delete_prohibited: Contact delete prohibited by status
statuses: statuses:
not_uniq: 'not uniq' not_uniq: 'not uniq'
delete_prohibited: Contact delete prohibited by status
country_code: country_code:
invalid: Country code is not valid, should be in ISO_3166-1 alpha 2 format (%{value}) invalid: Country code is not valid, should be in ISO_3166-1 alpha 2 format (%{value})
disclosed_attributes: disclosed_attributes:

View file

@ -6,3 +6,5 @@ en:
It was associated with registrant %{old_registrant_code} It was associated with registrant %{old_registrant_code}
and contacts %{old_contacts_codes}. and contacts %{old_contacts_codes}.
contact_update: Contact %{contact} has been updated by registrant 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

View file

@ -0,0 +1,6 @@
en:
registrar:
admin_contacts:
update:
replaced: Admin contacts have been successfully replaced.
replaced: Technical contacts have been successfully replaced.

View file

@ -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.

View file

@ -4,6 +4,7 @@ en:
new: new:
header: Bulk change header: Bulk change
technical_contact: Technical contact technical_contact: Technical contact
admin_contact: Admin contact
nameserver: Nameserver nameserver: Nameserver
bulk_transfer: Bulk transfer bulk_transfer: Bulk transfer
bulk_renew: Bulk renew bulk_renew: Bulk renew
@ -17,6 +18,19 @@ en:
Replace technical contact specified in "current contact ID" with the one in "new Replace technical contact specified in "current contact ID" with the one in "new
contact ID" on any domain registered under this registrar 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: nameserver_form:
ip_hint: One IP per line ip_hint: One IP per line
replace_btn: Replace nameserver replace_btn: Replace nameserver
@ -38,3 +52,5 @@ en:
domain_ids: Domains for bulk renewal domain_ids: Domains for bulk renewal
current_balance: Current balance current_balance: Current balance
period: Period period: Period
affected_domains: Affected domains
skipped_domains: Skipped domains

View file

@ -5,3 +5,7 @@ en:
replaced: Technical contacts have been successfully replaced. replaced: Technical contacts have been successfully replaced.
affected_domains: Affected domains affected_domains: Affected domains
skipped_domains: Skipped domains skipped_domains: Skipped domains
process_request:
affected_domains: Affected domains
skipped_domains: Skipped domains
replaced: Technical contacts have been successfully replaced.

View file

@ -64,6 +64,7 @@ Rails.application.routes.draw do
get ':id/transfer_info', to: 'domains#transfer_info', constraints: { id: /.*/ } get ':id/transfer_info', to: 'domains#transfer_info', constraints: { id: /.*/ }
post 'transfer', to: 'domains#transfer' post 'transfer', to: 'domains#transfer'
patch 'contacts', to: 'domains/contacts#update' patch 'contacts', to: 'domains/contacts#update'
patch 'admin_contacts', to: 'domains/admin_contacts#update'
post 'renew/bulk', to: 'domains/renews#bulk_renew' post 'renew/bulk', to: 'domains/renews#bulk_renew'
end end
end end
@ -91,6 +92,7 @@ Rails.application.routes.draw do
end end
resources :auctions, only: %i[index show update], param: :uuid resources :auctions, only: %i[index show update], param: :uuid
resources :contact_requests, only: %i[create update], param: :id
resources :bounces, only: %i[create] resources :bounces, only: %i[create]
end end
@ -136,6 +138,7 @@ Rails.application.routes.draw do
resource :bulk_change, controller: :bulk_change, only: :new resource :bulk_change, controller: :bulk_change, only: :new
post '/bulk_renew/new', to: 'bulk_change#bulk_renew', as: :bulk_renew post '/bulk_renew/new', to: 'bulk_change#bulk_renew', as: :bulk_renew
resource :tech_contacts, only: :update resource :tech_contacts, only: :update
resource :admin_contacts, only: :update
resource :nameservers, only: :update resource :nameservers, only: :update
resources :contacts, constraints: {:id => /[^\/]+(?=#{ ActionController::Renderers::RENDERERS.map{|e| "\\.#{e}\\z"}.join("|") })|[^\/]+/} do resources :contacts, constraints: {:id => /[^\/]+(?=#{ ActionController::Renderers::RENDERERS.map{|e| "\\.#{e}\\z"}.join("|") })|[^\/]+/} do
member do member do

View file

@ -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": []
}
}
```

View file

@ -1,7 +1,7 @@
# Domain contacts # Tech domain contacts
## PATCH https://repp.internet.ee/v1/domains/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 ### Example request
``` ```

8
test/fixtures/contact_requests.yml vendored Normal file
View file

@ -0,0 +1,8 @@
new:
whois_record_id: 1
email: aaa@bbb.com
name: Testname
status: new
secret: somesecret
valid_to: 2010-07-05

View file

@ -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

View file

@ -2,7 +2,7 @@ require 'test_helper'
class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest
def setup def setup
@api_key = "Basic #{ENV['api_shared_key']}" @api_key = "Basic #{ENV['rwhois_bounces_api_shared_key']}"
@headers = { "Authorization": "#{@api_key}" } @headers = { "Authorization": "#{@api_key}" }
@json_body = { "data": valid_bounce_request }.as_json @json_body = { "data": valid_bounce_request }.as_json
end end

View file

@ -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

View file

@ -26,7 +26,7 @@ class EppContactCheckBaseTest < EppTestCase
response_xml = Nokogiri::XML(response.body) response_xml = Nokogiri::XML(response.body)
assert_epp_response :completed_successfully 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 end
def test_contact_is_available def test_contact_is_available
@ -52,7 +52,8 @@ class EppContactCheckBaseTest < EppTestCase
end end
def test_contact_is_unavailable 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 request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
@ -98,6 +99,58 @@ class EppContactCheckBaseTest < EppTestCase
assert_equal 3, response_xml.xpath('//contact:cd', contact: xml_schema).size assert_equal 3, response_xml.xpath('//contact:cd', contact: xml_schema).size
end 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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<check>
<contact:check xmlns:contact="https://epp.tld.ee/schema/contact-ee-1.1.xsd">
<contact:id>BESTNAMES:JOHN-001</contact:id>
</contact:check>
</check>
</command>
</epp>
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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<check>
<contact:check xmlns:contact="https://epp.tld.ee/schema/contact-ee-1.1.xsd">
<contact:id>JOHN-001</contact:id>
</contact:check>
</check>
</command>
</epp>
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 private
def xml_schema def xml_schema

View file

@ -27,6 +27,60 @@ class EppContactDeleteBaseTest < EppTestCase
assert_epp_response :completed_successfully assert_epp_response :completed_successfully
end end
def test_delete_contact_with_server_delete_prohibited
contact = deletable_contact
contact.update(statuses: Contact::SERVER_DELETE_PROHIBITED)
assert contact.statuses.include? Contact::SERVER_DELETE_PROHIBITED
contact.update_columns(code: contact.code.upcase)
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<delete>
<contact:delete xmlns:contact="https://epp.tld.ee/schema/contact-ee-1.1.xsd">
<contact:id>#{contact.code.upcase}</contact:id>
</contact:delete>
</delete>
</command>
</epp>
XML
post epp_delete_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
assert Contact.exists?(id: contact.id)
assert_epp_response :object_status_prohibits_operation
end
def test_delete_contact_with_client_delete_prohibited
contact = deletable_contact
contact.update(statuses: Contact::CLIENT_DELETE_PROHIBITED)
assert contact.statuses.include? Contact::CLIENT_DELETE_PROHIBITED
contact.update_columns(code: contact.code.upcase)
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<delete>
<contact:delete xmlns:contact="https://epp.tld.ee/schema/contact-ee-1.1.xsd">
<contact:id>#{contact.code.upcase}</contact:id>
</contact:delete>
</delete>
</command>
</epp>
XML
post epp_delete_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
assert Contact.exists?(id: contact.id)
assert_epp_response :object_status_prohibits_operation
end
def test_undeletable_cannot_be_deleted def test_undeletable_cannot_be_deleted
contact = contacts(:john) contact = contacts(:john)
assert_not contact.deletable? assert_not contact.deletable?

View file

@ -44,6 +44,58 @@ class EppContactInfoBaseTest < EppTestCase
contact: xml_schema).text contact: xml_schema).text
end 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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<info>
<contact:info xmlns:contact="https://epp.tld.ee/schema/contact-ee-1.1.xsd">
<contact:id>TEST:JOHN-001</contact:id>
</contact:info>
</info>
</command>
</epp>
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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<info>
<contact:info xmlns:contact="https://epp.tld.ee/schema/contact-ee-1.1.xsd">
<contact:id>JOHN-001</contact:id>
</contact:info>
</info>
</command>
</epp>
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 def test_hides_password_and_name_when_current_registrar_is_not_sponsoring
non_sponsoring_registrar = registrars(:goodnames) non_sponsoring_registrar = registrars(:goodnames)
@contact.update!(registrar: non_sponsoring_registrar) @contact.update!(registrar: non_sponsoring_registrar)

View file

@ -123,6 +123,46 @@ class EppDomainUpdateBaseTest < EppTestCase
assert_verification_and_notification_emails assert_verification_and_notification_emails
end end
def test_domain_should_doesnt_have_pending_update_when_updated_registrant_with_same_idents_data
assert_not @domain.statuses.include? "pendingUpdate"
old_registrant = @domain.registrant
new_registrant = contacts(:william).becomes(Registrant)
new_registrant.update(ident: old_registrant.ident)
new_registrant.update(ident_country_code: old_registrant.ident_country_code)
new_registrant.update(ident_type: old_registrant.ident_type)
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<update>
<domain:update xmlns:domain="https://epp.tld.ee/schema/domain-eis-1.0.xsd">
<domain:name>#{@domain.name}</domain:name>
<domain:chg>
<domain:registrant verified="no">#{new_registrant.code}</domain:registrant>
</domain:chg>
</domain:update>
</update>
<extension>
<eis:extdata xmlns:eis="https://epp.tld.ee/schema/eis-1.0.xsd">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
post epp_update_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
@domain.reload
assert_epp_response :completed_successfully
assert_equal @domain.registrant, new_registrant
assert_not @domain.statuses.include? "pendingUpdate"
end
def test_requires_verification_from_current_registrant_when_not_yet_verified_by_registrar def test_requires_verification_from_current_registrant_when_not_yet_verified_by_registrar
Setting.request_confirmation_on_registrant_change_enabled = true Setting.request_confirmation_on_registrant_change_enabled = true
new_registrant = contacts(:william) new_registrant = contacts(:william)

View file

@ -37,6 +37,29 @@ class ReppV1DomainsBulkRenewTest < ActionDispatch::IntegrationTest
end end
end end
def test_keeps_update_prohibited_status
domain = domains(:shop)
domain.update(statuses: [DomainStatus::CLIENT_UPDATE_PROHIBITED, DomainStatus::SERVER_UPDATE_PROHIBITED])
payload = {
"domains": [
'shop.test'
],
"renew_period": "1y"
}
assert_changes -> { Domain.find_by(name: 'shop.test').valid_to } do
post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
assert json[:data][:updated_domains].include? 'shop.test'
end
domain.reload
assert_equal domain.statuses, [DomainStatus::CLIENT_UPDATE_PROHIBITED, DomainStatus::SERVER_UPDATE_PROHIBITED]
end
def test_throws_error_when_domain_not_renewable def test_throws_error_when_domain_not_renewable
payload = { payload = {
"domains": [ "domains": [

View file

@ -1,5 +1,4 @@
require "test_helper" require "test_helper"
class DomainUpdateConfirmJobTest < ActiveSupport::TestCase class DomainUpdateConfirmJobTest < ActiveSupport::TestCase
def setup def setup
super super
@ -19,6 +18,22 @@ class DomainUpdateConfirmJobTest < ActiveSupport::TestCase
super super
end 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 def test_rejected_registrant_verification_notifies_registrar
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED)

View file

@ -234,6 +234,19 @@ class ForceDeleteTest < ActionMailer::TestCase
assert_includes(@domain.statuses, asserted_status) assert_includes(@domain.statuses, asserted_status)
end end
def test_client_hold_prohibits_manual_inzone
@domain.update(valid_to: Time.zone.parse('2012-08-05'))
@domain.update(template_name: 'legal_person')
travel_to Time.zone.parse('2010-07-05')
@domain.schedule_force_delete(type: :soft)
travel_to Time.zone.parse('2010-08-21')
Domains::ClientHold::SetClientHold.run!
@domain.reload
@domain.statuses << DomainStatus::SERVER_MANUAL_INZONE
assert_not @domain.valid?
end
def test_force_delete_soft_year_ahead_not_sets_client_hold_before_threshold def test_force_delete_soft_year_ahead_not_sets_client_hold_before_threshold
asserted_status = DomainStatus::CLIENT_HOLD asserted_status = DomainStatus::CLIENT_HOLD

View file

@ -38,6 +38,20 @@ class WhiteIpTest < ActiveSupport::TestCase
assert white_ip.valid? assert white_ip.valid?
end 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 private
def valid_white_ip def valid_white_ip

View file

@ -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

View file

@ -16,8 +16,8 @@ class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase
visit registrar_domains_url visit registrar_domains_url
click_link 'Bulk change' click_link 'Bulk change'
fill_in 'Current contact ID', with: 'william-001' find('.current_tech_contact').fill_in 'Current contact ID', with: 'william-001'
fill_in 'New contact ID', with: 'john-001' find('.new_tech_contact').fill_in 'New contact ID', with: 'john-001'
click_on 'Replace technical contacts' click_on 'Replace technical contacts'
assert_requested request_stub assert_requested request_stub
@ -36,8 +36,8 @@ class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase
visit registrar_domains_url visit registrar_domains_url
click_link 'Bulk change' click_link 'Bulk change'
fill_in 'Current contact ID', with: 'william-001' find('.current_tech_contact').fill_in 'Current contact ID', with: 'william-001'
fill_in 'New contact ID', with: 'john-001' find('.new_tech_contact').fill_in 'New contact ID', with: 'john-001'
click_on 'Replace technical contacts' click_on 'Replace technical contacts'
assert_text 'epic fail' assert_text 'epic fail'