Merge remote-tracking branch 'origin/master' into registrant-api-fetch-improvements

This commit is contained in:
Karl Erik Õunapuu 2020-11-30 10:34:28 +02:00
commit 3761fe7bbd
No known key found for this signature in database
GPG key ID: C9DD647298A34764
97 changed files with 2824 additions and 821 deletions

View file

@ -1,3 +1,18 @@
27.11.2020
* Refactored delete confirmation for interactors [#1753](https://github.com/internetee/registry/issues/1753)
24.11.2020
* Added subnet support for list of allowed IPs [#983](https://github.com/internetee/registry/issues/983)
* Added contact endpoint to Restful EPP API [#1580](https://github.com/internetee/registry/issues/1580)
20.11.2020
* Registrant confirmation over Registrant API [#1742](https://github.com/internetee/registry/pull/1742)
* Refactored forceDelete cancellation for interactors [#1743](https://github.com/internetee/registry/issues/1743)
19.11.2020
* Only sponsoring registrar has access to private contact's details [#1745](https://github.com/internetee/registry/issues/1745)
* Refactor ForceDelete [#1740](https://github.com/internetee/registry/issues/1740)
13.11.2020
* Fixed per registrar epp session limit [#729](https://github.com/internetee/registry/issues/729)
* Correct error code is returned on reaching session limit [#587](https://github.com/internetee/registry/issues/587)

View file

@ -1,6 +1,7 @@
source 'https://rubygems.org'
# core
gem 'active_interaction', '~> 3.8'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'iso8601', '0.12.1' # for dates and times
gem 'rails', '~> 6.0'
@ -35,8 +36,6 @@ gem 'select2-rails', '3.5.9.3' # for autocomplete
gem 'cancancan'
gem 'devise', '~> 4.7'
gem 'grape'
# registry specfic
gem 'data_migrate', '~> 6.1'
gem 'isikukood' # for EE-id validation

View file

@ -112,6 +112,8 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_interaction (3.8.3)
activemodel (>= 4, < 7)
activejob (6.0.3.3)
activesupport (= 6.0.3.3)
globalid (>= 0.3.6)
@ -196,28 +198,6 @@ GEM
docile (1.3.2)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dry-configurable (0.11.6)
concurrent-ruby (~> 1.0)
dry-core (~> 0.4, >= 0.4.7)
dry-equalizer (~> 0.2)
dry-container (0.7.2)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1, >= 0.1.3)
dry-core (0.4.9)
concurrent-ruby (~> 1.0)
dry-equalizer (0.3.0)
dry-inflector (0.2.0)
dry-logic (1.0.7)
concurrent-ruby (~> 1.0)
dry-core (~> 0.2)
dry-equalizer (~> 0.2)
dry-types (1.4.0)
concurrent-ruby (~> 1.0)
dry-container (~> 0.3)
dry-core (~> 0.4, >= 0.4.4)
dry-equalizer (~> 0.3)
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 1.0, >= 1.0.2)
erubi (1.9.0)
erubis (2.7.0)
execjs (2.7.0)
@ -226,13 +206,6 @@ GEM
thor (~> 0.14)
globalid (0.4.2)
activesupport (>= 4.2.0)
grape (1.4.0)
activesupport
builder
dry-types (>= 1.1)
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
gyoku (1.3.1)
builder (>= 2.1.2)
haml (5.1.2)
@ -312,8 +285,6 @@ GEM
multi_json (1.15.0)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
mustermann-grape (1.0.1)
mustermann (>= 1.0.0)
netrc (0.11.0)
nio4r (2.5.4)
nokogiri (1.10.10)
@ -357,8 +328,6 @@ GEM
que (~> 0.8)
sinatra
rack (2.2.3)
rack-accept (0.4.5)
rack (>= 0.4)
rack-oauth2 (1.16.0)
activesupport
attr_required
@ -521,6 +490,7 @@ PLATFORMS
ruby
DEPENDENCIES
active_interaction (~> 3.8)
activerecord-import
airbrake
bootsnap (>= 1.1.0)
@ -542,7 +512,6 @@ DEPENDENCIES
epp!
epp-xml (= 1.1.0)!
figaro (= 1.1.1)
grape
haml (~> 5.0)
isikukood
iso8601 (= 0.12.1)

View file

@ -1,16 +0,0 @@
module Repp
class AccountV1 < Grape::API
version 'v1', using: :path
resource :accounts do
desc 'Return current cash account balance'
get 'balance' do
@response = {
balance: current_user.registrar.cash_account.balance,
currency: current_user.registrar.cash_account.currency
}
end
end
end
end

View file

@ -1,65 +0,0 @@
module Repp
class API < Grape::API
format :json
prefix :repp
http_basic do |username, password|
@current_user ||= ApiUser.find_by(username: username, plain_text_password: password)
if @current_user
true
else
error! I18n.t('api_user_not_found'), 401
end
end
before do
webclient_request = ENV['webclient_ips'].split(',').map(&:strip).include?(request.ip)
unless webclient_request
error! I18n.t('api.authorization.ip_not_allowed', ip: request.ip), 401 unless @current_user.registrar.api_ip_white?(request.ip)
end
if @current_user.cannot?(:view, :repp)
error! I18n.t('no_permission'), 401 unless @current_user.registrar.api_ip_white?(request.ip)
end
next if Rails.env.test? || Rails.env.development?
message = 'Certificate mismatch! Cert common name should be:'
request_name = env['HTTP_SSL_CLIENT_S_DN_CN']
if webclient_request
webclient_cert_name = ENV['webclient_cert_common_name'] || 'webclient'
error! "Webclient #{message} #{webclient_cert_name}", 401 if webclient_cert_name != request_name
else
unless @current_user.pki_ok?(request.env['HTTP_SSL_CLIENT_CERT'],
request.env['HTTP_SSL_CLIENT_S_DN_CN'])
error! "#{message} #{@current_user.username}", 401
end
end
end
helpers do
attr_reader :current_user
end
after do
ApiLog::ReppLog.create({
request_path: request.path,
request_method: request.request_method,
request_params: request.params.except('route_info').to_json,
response: @response.to_json,
response_code: status,
api_user_name: current_user.try(:username),
api_user_registrar: current_user.try(:registrar).try(:to_s),
ip: request.ip,
uuid: request.try(:uuid)
})
end
mount Repp::DomainV1
mount Repp::ContactV1
mount Repp::AccountV1
mount Repp::DomainTransfersV1
mount Repp::NameserversV1
mount Repp::DomainContactsV1
end
end

View file

@ -1,35 +0,0 @@
module Repp
class ContactV1 < Grape::API
version 'v1', using: :path
resource :contacts do
desc 'Return list of contact'
params do
optional :limit, type: Integer, values: (1..200).to_a, desc: 'How many contacts to show'
optional :offset, type: Integer, desc: 'Contact number to start at'
optional :details, type: String, values: %w(true false), desc: 'Whether to include details'
end
get '/' do
limit = params[:limit] || 200
offset = params[:offset] || 0
if params[:details] == 'true'
contacts = current_user.registrar.contacts.limit(limit).offset(offset)
unless Contact.address_processing?
attributes = Contact.attribute_names - Contact.address_attribute_names
contacts = contacts.select(attributes)
end
else
contacts = current_user.registrar.contacts.limit(limit).offset(offset).pluck(:code)
end
@response = {
contacts: contacts,
total_number_of_records: current_user.registrar.contacts.count
}
end
end
end
end

View file

@ -1,47 +0,0 @@
module Repp
class DomainContactsV1 < Grape::API
version 'v1', using: :path
resource :domains do
resource :contacts do
patch '/' do
current_contact = current_user.registrar.contacts
.find_by(code: params[:current_contact_id])
new_contact = current_user.registrar.contacts.find_by(code: params[:new_contact_id])
unless current_contact
error!({ error: { type: 'invalid_request_error',
param: 'current_contact_id',
message: "No such contact: #{params[:current_contact_id]}"} },
:bad_request)
end
unless new_contact
error!({ error: { type: 'invalid_request_error',
param: 'new_contact_id',
message: "No such contact: #{params[:new_contact_id]}" } },
:bad_request)
end
if new_contact.invalid?
error!({ error: { type: 'invalid_request_error',
param: 'new_contact_id',
message: 'New contact must be valid' } },
:bad_request)
end
if current_contact == new_contact
error!({ error: { type: 'invalid_request_error',
message: 'New contact ID must be different from current' \
' contact ID' } },
:bad_request)
end
affected_domains, skipped_domains = TechDomainContact
.replace(current_contact, new_contact)
@response = { affected_domains: affected_domains, skipped_domains: skipped_domains }
end
end
end
end
end

View file

@ -1,48 +0,0 @@
module Repp
class DomainTransfersV1 < Grape::API
version 'v1', using: :path
resource :domain_transfers do
post '/' do
params do
requires :data, type: Hash do
requires :domainTransfers, type: Array do
requires :domainName, type: String, allow_blank: false
requires :transferCode, type: String, allow_blank: false
end
end
end
new_registrar = current_user.registrar
domain_transfers = params['data']['domainTransfers']
successful_domain_transfers = []
errors = []
domain_transfers.each do |domain_transfer|
domain_name = domain_transfer['domainName']
transfer_code = domain_transfer['transferCode']
domain = Domain.find_by(name: domain_name)
if domain
if domain.transfer_code == transfer_code
DomainTransfer.request(domain, new_registrar)
successful_domain_transfers << { type: 'domain_transfer', attributes: { domain_name: domain.name } }
else
errors << { title: "#{domain_name} transfer code is wrong" }
end
else
errors << { title: "#{domain_name} does not exist" }
end
end
if errors.none?
status 200
@response = { data: successful_domain_transfers }
else
status 400
@response = { errors: errors }
end
end
end
end
end

View file

@ -1,50 +0,0 @@
module Repp
class DomainV1 < Grape::API
version 'v1', using: :path
resource :domains do
desc 'Return list of domains'
params do
optional :limit, type: Integer, values: (1..200).to_a, desc: 'How many domains to show'
optional :offset, type: Integer, desc: 'Domain number to start at'
optional :details, type: String, values: %w(true false), desc: 'Whether to include details'
end
get '/' do
limit = params[:limit] || 200
offset = params[:offset] || 0
if params[:details] == 'true'
domains = current_user.registrar.domains.limit(limit).offset(offset)
else
domains = current_user.registrar.domains.limit(limit).offset(offset).pluck(:name)
end
@response = {
domains: domains,
total_number_of_records: current_user.registrar.domains.count
}
end
# example: curl -u registrar1:password localhost:3000/repp/v1/domains/1/transfer_info -H "Auth-Code: authinfopw1"
get '/:id/transfer_info', requirements: { id: /.*/ } do
ident = params[:id]
domain = ident.match?(/\A[0-9]+\z/) ? Domain.find_by(id: ident) : Domain.find_by_idn(ident)
error! I18n.t('errors.messages.epp_domain_not_found'), 404 unless domain
error! I18n.t('errors.messages.epp_authorization_error'), 401 unless domain.transfer_code.eql? request.headers['Auth-Code']
contact_repp_json = proc{|contact|
contact.as_json.slice("code", "name", "ident", "ident_type", "ident_country_code", "phone", "email", "street", "city", "zip","country_code", "statuses")
}
@response = {
domain: domain.name,
registrant: contact_repp_json.call(domain.registrant),
admin_contacts: domain.admin_contacts.map{|e| contact_repp_json.call(e)},
tech_contacts: domain.tech_contacts.map{|e| contact_repp_json.call(e)}
}
end
end
end
end

View file

@ -1,49 +0,0 @@
module Repp
class NameserversV1 < Grape::API
version 'v1', using: :path
resource 'registrar/nameservers' do
put '/' do
params do
requires :data, type: Hash, allow_blank: false do
requires :type, type: String, allow_blank: false
requires :id, type: String, allow_blank: false
optional :domains, type: Array
requires :attributes, type: Hash, allow_blank: false do
requires :hostname, type: String, allow_blank: false
requires :ipv4, type: Array
requires :ipv6, type: Array
end
end
end
hostname = params[:data][:id]
unless current_user.registrar.nameservers.exists?(hostname: hostname)
error!({ errors: [{ title: "Hostname #{hostname} does not exist" }] }, 404)
end
new_attributes = {
hostname: params[:data][:attributes][:hostname],
ipv4: params[:data][:attributes][:ipv4],
ipv6: params[:data][:attributes][:ipv6],
}
domains = params[:data][:domains] || []
begin
affected_domains = current_user.registrar.replace_nameservers(hostname, new_attributes,
domains: domains)
rescue ActiveRecord::RecordInvalid => e
error!({ errors: e.record.errors.full_messages.map { |error| { title: error } } }, 400)
end
status 200
@response = { data: { type: 'nameserver',
id: params[:data][:attributes][:hostname],
attributes: params[:data][:attributes] },
affected_domains: affected_domains }
end
end
end
end

View file

@ -4,26 +4,15 @@ module Admin
def create
authorize! :manage, domain
notice = t('.scheduled')
domain.transaction do
domain.schedule_force_delete(type: force_delete_type)
domain.registrar.notifications.create!(text: t('force_delete_set_on_domain',
domain_name: domain.name,
outzone_date: domain.outzone_date,
purge_date: domain.purge_date))
notify_by_email if notify_by_email?
result = domain.schedule_force_delete(type: force_delete_type,
notify_by_email: notify_by_email?)
notice = result.errors.messages[:domain].first unless result.valid?
end
redirect_to edit_admin_domain_url(domain), notice: t('.scheduled')
end
def notify_by_email
if force_delete_type == :fast_track
send_email
domain.update(contact_notification_sent_date: Time.zone.today)
else
domain.update(template_name: domain.notification_template)
end
redirect_to edit_admin_domain_url(domain), notice: notice
end
def destroy
@ -42,13 +31,6 @@ module Admin
ActiveRecord::Type::Boolean.new.cast(params[:notify_by_email])
end
def send_email
DomainDeleteMailer.forced(domain: domain,
registrar: domain.registrar,
registrant: domain.registrant,
template_name: domain.notification_template).deliver_now
end
def force_delete_type
soft_delete? ? :soft : :fast_track
end

View file

@ -0,0 +1,119 @@
require 'serializers/registrant_api/domain'
module Api
module V1
module Registrant
class ConfirmsController < ::Api::V1::Registrant::BaseController
skip_before_action :authenticate, :set_paper_trail_whodunnit
before_action :set_domain, only: %i[index update]
before_action :verify_action, only: %i[index update]
before_action :verify_decision, only: %i[update]
def index
res = {
domain_name: @domain.name,
current_registrant: serialized_registrant(@domain.registrant),
}
unless delete_action?
res[:new_registrant] = serialized_registrant(@domain.pending_registrant)
end
render json: res, status: :ok
end
def update
verification = RegistrantVerification.new(domain_id: @domain.id,
verification_token: verify_params[:token])
unless delete_action? ? delete_action(verification) : change_action(verification)
head :bad_request
return
end
render json: { domain_name: @domain.name,
current_registrant: serialized_registrant(current_registrant),
status: params[:decision] }, status: :ok
end
private
def initiator
"email link, #{I18n.t(:user_not_authenticated)}"
end
def current_registrant
confirmed? && !delete_action? ? @domain.pending_registrant : @domain.registrant
end
def confirmed?
verify_params[:decision] == 'confirmed'
end
def change_action(verification)
if confirmed?
verification.domain_registrant_change_confirm!(initiator)
else
verification.domain_registrant_change_reject!(initiator)
end
end
def delete_action(verification)
if confirmed?
verification.domain_registrant_delete_confirm!(initiator)
else
verification.domain_registrant_delete_reject!(initiator)
end
end
def serialized_registrant(registrant)
{
name: registrant.try(:name),
ident: registrant.try(:ident),
country: registrant.try(:ident_country_code),
}
end
def verify_params
params do |p|
p.require(:name)
p.require(:token)
p.permit(:decision)
end
end
def delete_action?
return true if params[:template] == 'delete'
false
end
def verify_decision
return if %w[confirmed rejected].include?(params[:decision])
head :not_found
end
def set_domain
@domain = Domain.find_by(name: verify_params[:name])
@domain ||= Domain.find_by(name_puny: verify_params[:name])
return if @domain
render json: { error: 'Domain not found' }, status: :not_found
end
def verify_action
action = if params[:template] == 'change'
@domain.registrant_update_confirmable?(verify_params[:token])
elsif params[:template] == 'delete'
@domain.registrant_delete_confirmable?(verify_params[:token])
end
return if action
render json: { error: 'Application expired or not found' }, status: :unauthorized
end
end
end
end
end

View file

@ -1,10 +1,9 @@
require 'deserializers/xml/contact_update'
require 'deserializers/xml/contact_create'
module Epp
class ContactsController < BaseController
before_action :find_contact, only: [:info, :update, :delete]
before_action :find_password, only: [:info, :update, :delete]
helper_method :address_processing?
def info
authorize! :info, @contact, @password
@ -21,25 +20,13 @@ module Epp
def create
authorize! :create, Epp::Contact
frame = params[:parsed_frame]
@contact = Epp::Contact.new(frame, current_user.registrar)
@contact.add_legal_file_to_new(frame)
@contact.generate_code
@contact = Epp::Contact.new(params[:parsed_frame], current_user.registrar)
collected_data = ::Deserializers::Xml::ContactCreate.new(params[:parsed_frame])
action = Actions::ContactCreate.new(@contact, collected_data.legal_document,
collected_data.ident)
if @contact.save
if !address_processing? && address_given?
@response_code = 1100
@response_description = t('epp.contacts.completed_without_address')
else
@response_code = 1000
@response_description = t('epp.contacts.completed')
end
render_epp_response '/epp/contacts/save'
else
handle_errors(@contact)
end
action_call_response(action: action)
end
def update
@ -52,29 +39,18 @@ module Epp
collected_data.ident,
current_user)
if action.call
if !address_processing? && address_given?
@response_code = 1100
@response_description = t('epp.contacts.completed_without_address')
else
@response_code = 1000
@response_description = t('epp.contacts.completed')
end
render_epp_response 'epp/contacts/save'
else
handle_errors(@contact)
end
action_call_response(action: action)
end
def delete
authorize! :delete, @contact, @password
if @contact.destroy_and_clean(params[:parsed_frame])
render_epp_response '/epp/contacts/delete'
else
action = Actions::ContactDelete.new(@contact, params[:legal_document])
unless action.call
handle_errors(@contact)
return
end
render_epp_response '/epp/contacts/delete'
end
def renew
@ -91,6 +67,26 @@ module Epp
private
def opt_addr?
!Contact.address_processing? && address_given?
end
def action_call_response(action:)
# rubocop:disable Style/AndOr
(handle_errors(@contact) and return) unless action.call
# rubocop:enable Style/AndOr
if opt_addr?
@response_code = 1100
@response_description = t('epp.contacts.completed_without_address')
else
@response_code = 1000
@response_description = t('epp.contacts.completed')
end
render_epp_response('epp/contacts/save')
end
def find_password
@password = params[:parsed_frame].css('authInfo pw').text
end
@ -129,8 +125,7 @@ module Epp
'postalInfo > addr > cc',
]
required_attributes.concat(address_attributes) if address_processing?
required_attributes.concat(address_attributes) if Contact.address_processing?
requires(*required_attributes)
ident = params[:parsed_frame].css('ident')
@ -206,9 +201,5 @@ module Epp
def address_given?
params[:parsed_frame].css('postalInfo addr').size != 0
end
def address_processing?
Contact.address_processing?
end
end
end

View file

@ -15,12 +15,12 @@ class Registrar
csv.each do |row|
domain_name = row['Domain']
transfer_code = row['Transfer code']
domain_transfers << { 'domainName' => domain_name, 'transferCode' => transfer_code }
domain_transfers << { 'domain_name' => domain_name, 'transfer_code' => transfer_code }
end
uri = URI.parse("#{ENV['repp_url']}domain_transfers")
uri = URI.parse("#{ENV['repp_url']}domains/transfer")
request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
request.body = { data: { domainTransfers: domain_transfers } }.to_json
request.body = { data: { domain_transfers: domain_transfers } }.to_json
request.basic_auth(current_registrar_user.username,
current_registrar_user.plain_text_password)

View file

@ -0,0 +1,11 @@
module Repp
module V1
class AccountsController < BaseController
def balance
resp = { balance: current_user.registrar.cash_account.balance,
currency: current_user.registrar.cash_account.currency }
render_success(data: resp)
end
end
end
end

View file

@ -3,9 +3,9 @@ module Repp
class AuctionsController < ActionController::API
def index
auctions = Auction.started
@response = { count: auctions.count, auctions: auctions_to_json(auctions) }
render json: { count: auctions.count,
auctions: auctions_to_json(auctions) }
render json: @response
end
private

View file

@ -0,0 +1,111 @@
module Repp
module V1
class BaseController < ActionController::API
rescue_from ActiveRecord::RecordNotFound, with: :not_found_error
before_action :authenticate_user
before_action :check_ip_restriction
attr_reader :current_user
before_action :set_paper_trail_whodunnit
rescue_from ActionController::ParameterMissing do |exception|
render json: { code: 2003, message: exception }, status: :bad_request
end
after_action do
ApiLog::ReppLog.create(
request_path: request.path, request_method: request.request_method,
request_params: request.params.except('route_info').to_json, uuid: request.try(:uuid),
response: @response.to_json, response_code: status, ip: request.ip,
api_user_name: current_user.try(:username),
api_user_registrar: current_user.try(:registrar).try(:to_s)
)
end
private
def set_paper_trail_whodunnit
::PaperTrail.request.whodunnit = current_user
end
def render_success(code: nil, message: nil, data: nil)
@response = { code: code || 1000, message: message || 'Command completed successfully',
data: data || {} }
render(json: @response, status: :ok)
end
def epp_errors
@epp_errors ||= []
end
def handle_errors(obj = nil, update: false)
@epp_errors ||= []
obj&.construct_epp_errors
@epp_errors += obj.errors[:epp_errors] if obj
format_epp_errors if update
@epp_errors.uniq!
render_epp_error
end
def format_epp_errors
@epp_errors.each_with_index do |error, index|
blocked_by_delete_prohibited?(error, index)
end
end
def blocked_by_delete_prohibited?(error, index)
if error[:code] == 2304 && error[:value][:val] == DomainStatus::SERVER_DELETE_PROHIBITED &&
error[:value][:obj] == 'status'
@epp_errors[index][:value][:val] = DomainStatus::PENDING_UPDATE
end
end
def render_epp_error(status = :bad_request, data = {})
@epp_errors ||= []
@epp_errors << { code: 2304, msg: 'Command failed' } if data != {}
@response = { code: @epp_errors[0][:code].to_i, message: @epp_errors[0][:msg], data: data }
render(json: @response, status: status)
end
def basic_token
pattern = /^Basic /
header = request.headers['Authorization']
header = header.gsub(pattern, '') if header&.match(pattern)
header.strip
end
def authenticate_user
username, password = Base64.urlsafe_decode64(basic_token).split(':')
@current_user ||= ApiUser.find_by(username: username, plain_text_password: password)
return if @current_user
raise(ArgumentError)
rescue NoMethodError, ArgumentError
@response = { code: 2202, message: 'Invalid authorization information' }
render(json: @response, status: :unauthorized)
end
def check_ip_restriction
allowed = @current_user.registrar.api_ip_white?(request.ip)
return if allowed
@response = { code: 2202,
message: I18n.t('registrar.authorization.ip_not_allowed', ip: request.ip) }
render(json: @response, status: :unauthorized)
end
def not_found_error
@response = { code: 2303, message: 'Object does not exist' }
render(json: @response, status: :not_found)
end
end
end
end

View file

@ -0,0 +1,137 @@
require 'serializers/repp/contact'
module Repp
module V1
class ContactsController < BaseController
before_action :find_contact, only: %i[show update destroy]
## GET /repp/v1/contacts
def index
record_count = current_user.registrar.contacts.count
contacts = showable_contacts(params[:details], params[:limit] || 200,
params[:offset] || 0)
@response = { contacts: contacts, total_number_of_records: record_count }
render(json: @response, status: :ok)
end
## GET /repp/v1/contacts/1
def show
serializer = ::Serializers::Repp::Contact.new(@contact,
show_address: Contact.address_processing?)
render_success(data: serializer.to_json)
end
## GET /repp/v1/contacts/check/1
def check
contact = Epp::Contact.find_by(code: params[:id])
data = { contact: { id: params[:id], available: contact.nil? } }
render_success(data: data)
end
## POST /repp/v1/contacts
def create
@contact = Epp::Contact.new(contact_params_with_address, current_user.registrar, epp: false)
action = Actions::ContactCreate.new(@contact, params[:legal_document],
contact_ident_params)
unless action.call
handle_errors(@contact)
return
end
render_success(create_update_success_body)
end
## PUT /repp/v1/contacts/1
def update
action = Actions::ContactUpdate.new(@contact, contact_params_with_address(required: false),
params[:legal_document],
contact_ident_params(required: false), current_user)
unless action.call
handle_errors(@contact)
return
end
render_success(create_update_success_body)
end
def destroy
action = Actions::ContactDelete.new(@contact, params[:legal_document])
unless action.call
handle_errors(@contact)
return
end
render_success
end
def contact_addr_present?
return false unless contact_addr_params.key?(:addr)
contact_addr_params[:addr].keys.any?
end
def create_update_success_body
{ code: opt_addr? ? 1100 : nil, data: { contact: { id: @contact.code } },
message: opt_addr? ? I18n.t('epp.contacts.completed_without_address') : nil }
end
def showable_contacts(details, limit, offset)
contacts = current_user.registrar.contacts.limit(limit).offset(offset)
return contacts.pluck(:code) unless details
contacts = contacts.map do |contact|
serializer = ::Serializers::Repp::Contact.new(contact,
show_address: Contact.address_processing?)
serializer.to_json
end
contacts
end
def opt_addr?
!Contact.address_processing? && contact_addr_present?
end
def find_contact
code = params[:id]
@contact = Epp::Contact.find_by!(code: code, registrar: current_user.registrar)
end
def contact_params_with_address(required: true)
return contact_create_params(required: required) unless contact_addr_params.key?(:addr)
addr = {}
contact_addr_params[:addr].each_key { |k| addr[k] = contact_addr_params[:addr][k] }
contact_create_params(required: required).merge(addr)
end
def contact_create_params(required: true)
params.require(:contact).require(%i[name email phone]) if required
params.require(:contact).permit(:name, :email, :phone, :id)
end
def contact_ident_params(required: true)
if required
params.require(:contact).require(:ident).require(%i[ident ident_type ident_country_code])
params.require(:contact).require(:ident).permit(:ident, :ident_type, :ident_country_code)
else
params.permit(contact: { ident: %i[ident ident_type ident_country_code] })
end
params[:contact][:ident]
end
def contact_addr_params
if Contact.address_processing?
params.require(:contact).require(:addr).require(%i[country_code city street zip])
params.require(:contact).require(:addr).permit(:country_code, :city, :street, :zip)
else
params.require(:contact).permit(addr: %i[country_code city street zip])
end
end
end
end
end

View file

@ -0,0 +1,42 @@
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
def update
@epp_errors ||= []
@epp_errors << { code: 2304, msg: 'New contact must be valid' } if @new_contact.invalid?
if @new_contact == @current_contact
@epp_errors << { code: 2304, msg: 'New contact must be different from current' }
end
return handle_errors if @epp_errors.any?
affected, skipped = TechDomainContact.replace(@current_contact, @new_contact)
@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
end

View file

@ -0,0 +1,94 @@
module Repp
module V1
class DomainsController < BaseController
before_action :set_authorized_domain, only: [:transfer_info]
def index
records = current_user.registrar.domains
domains = records.limit(limit).offset(offset)
domains = domains.pluck(:name) unless index_params[:details] == 'true'
render_success(data: { domains: domains, total_number_of_records: records.count })
end
def transfer_info
contact_fields = %i[code name ident ident_type ident_country_code phone email street city
zip country_code statuses]
data = {
domain: @domain.name,
registrant: @domain.registrant.as_json(only: contact_fields),
admin_contacts: @domain.admin_contacts.map { |c| c.as_json(only: contact_fields) },
tech_contacts: @domain.tech_contacts.map { |c| c.as_json(only: contact_fields) },
}
render_success(data: data)
end
def transfer
@errors ||= []
@successful = []
transfer_params[:domain_transfers].each do |transfer|
initiate_transfer(transfer)
end
render_success(data: { success: @successful, failed: @errors })
end
def initiate_transfer(transfer)
domain = Epp::Domain.find_or_initialize_by(name: transfer[:domain_name])
action = Actions::DomainTransfer.new(domain, transfer[:transfer_code],
current_user.registrar)
if action.call
@successful << { type: 'domain_transfer', domain_name: domain.name }
else
@errors << { type: 'domain_transfer', domain_name: domain.name,
errors: domain.errors[:epp_errors] }
end
end
private
def transfer_params
params.require(:data).require(:domain_transfers).each do |t|
t.require(:domain_name)
t.permit(:domain_name)
t.require(:transfer_code)
t.permit(:transfer_code)
end
params.require(:data).permit(domain_transfers: %i[domain_name transfer_code])
end
def transfer_info_params
params.require(:id)
params.permit(:id)
end
def set_authorized_domain
@epp_errors ||= []
h = {}
h[transfer_info_params[:id].match?(/\A[0-9]+\z/) ? :id : :name] = transfer_info_params[:id]
@domain = Domain.find_by!(h)
return if @domain.transfer_code.eql?(request.headers['Auth-Code'])
@epp_errors << { code: 2202, msg: I18n.t('errors.messages.epp_authorization_error') }
handle_errors
end
def limit
index_params[:limit] || 200
end
def offset
index_params[:offset] || 0
end
def index_params
params.permit(:limit, :offset, :details)
end
end
end
end

View file

@ -0,0 +1,51 @@
module Repp
module V1
module Registrar
class NameserversController < BaseController
before_action :verify_nameserver_existance, only: %i[update]
def update
domains = params[:data][:domains] || []
affected = current_user.registrar
.replace_nameservers(hostname,
hostname_params[:data][:attributes],
domains: domains)
render_success(data: data_format_for_success(affected))
rescue ActiveRecord::RecordInvalid => e
handle_errors(e.record)
end
private
def data_format_for_success(affected_domains)
{
type: 'nameserver',
id: params[:data][:attributes][:hostname],
attributes: params[:data][:attributes],
affected_domains: affected_domains,
}
end
def hostname_params
params.require(:data).require(%i[type id])
params.require(:data).require(:attributes).require([:hostname])
params.permit(data: [
:type, :id,
{ domains: [],
attributes: [:hostname, { ipv4: [], ipv6: [] }] }
])
end
def hostname
hostname_params[:data][:id]
end
def verify_nameserver_existance
current_user.registrar.nameservers.find_by!(hostname: hostname)
end
end
end
end
end

View file

@ -3,8 +3,9 @@ module Repp
class RetainedDomainsController < ActionController::API
def index
domains = RetainedDomains.new(query_params)
@response = { count: domains.count, domains: domains.to_jsonable }
render json: { count: domains.count, domains: domains.to_jsonable }
render json: @response
end
def query_params

View file

@ -3,7 +3,7 @@ module ObjectVersionsHelper
version.object_changes.to_h.each do |key, value|
method_name = "#{key}=".to_sym
if new_object.respond_to?(method_name)
new_object.public_send(method_name, value.last)
new_object.public_send(method_name, event_value(version, value))
end
end
end
@ -12,4 +12,10 @@ module ObjectVersionsHelper
field_names = model.column_names
version.object.to_h.select { |key, _value| field_names.include?(key) }
end
private
def event_value(version, val)
version.event == 'destroy' ? val.first : val.last
end
end

View file

@ -0,0 +1,7 @@
module CancelForceDeleteInteraction
class Base < ActiveInteraction::Base
object :domain,
class: Domain,
description: 'Domain to cancel ForceDelete on'
end
end

View file

@ -0,0 +1,10 @@
module CancelForceDeleteInteraction
class CancelForceDelete < Base
def execute
compose(RemoveForceDeleteStatuses, inputs)
compose(RestoreStatusesBeforeForceDelete, inputs)
compose(ClearForceDeleteData, inputs)
compose(NotifyRegistrar, inputs)
end
end
end

View file

@ -0,0 +1,10 @@
module CancelForceDeleteInteraction
class ClearForceDeleteData < Base
def execute
domain.force_delete_data = nil
domain.force_delete_date = nil
domain.force_delete_start = nil
domain.save(validate: false)
end
end
end

View file

@ -0,0 +1,8 @@
module CancelForceDeleteInteraction
class NotifyRegistrar < Base
def execute
domain.registrar.notifications.create!(text: I18n.t('force_delete_cancelled',
domain_name: domain.name))
end
end
end

View file

@ -0,0 +1,11 @@
module CancelForceDeleteInteraction
class RemoveForceDeleteStatuses < Base
def execute
domain.statuses.delete(DomainStatus::FORCE_DELETE)
domain.statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED)
domain.statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED)
domain.statuses.delete(DomainStatus::CLIENT_HOLD)
domain.save(validate: false)
end
end
end

View file

@ -0,0 +1,9 @@
module CancelForceDeleteInteraction
class RestoreStatusesBeforeForceDelete < Base
def execute
domain.statuses = domain.statuses_before_force_delete
domain.statuses_before_force_delete = nil
domain.save(validate: false)
end
end
end

View file

@ -0,0 +1,22 @@
module DomainDeleteConfirmInteraction
class SendRequest < ActiveInteraction::Base
object :domain,
class: Domain,
description: 'Domain to send delete confirmation'
def execute
log
DomainDeleteMailer.confirmation_request(domain: domain,
registrar: domain.registrar,
registrant: domain.registrant).deliver_later
end
private
def log
message = "Send DomainDeleteMailer#confirm email for domain #{domain.name} (##{domain.id})" \
" to #{domain.registrant.email}"
Rails.logger.info(message)
end
end
end

View file

@ -0,0 +1,16 @@
module ForceDeleteInteraction
class Base < ActiveInteraction::Base
object :domain,
class: Domain,
description: 'Domain to set ForceDelete on'
symbol :type,
default: :fast_track,
description: 'Force delete type, might be :fast_track or :soft'
boolean :notify_by_email,
default: false,
description: 'Do we need to send email notification'
validates :type, inclusion: { in: %i[fast_track soft] }
end
end

View file

@ -0,0 +1,11 @@
module ForceDeleteInteraction
class CheckDiscarded < Base
def execute
return true unless domain.discarded?
message = 'Force delete procedure cannot be scheduled while a domain is discarded'
errors.add(:domain, message)
end
end
end

View file

@ -0,0 +1,21 @@
module ForceDeleteInteraction
class NotifyByEmail < Base
def execute
return unless notify_by_email
if type == :fast_track
send_email
domain.update(contact_notification_sent_date: Time.zone.today)
else
domain.update(template_name: domain.notification_template)
end
end
def send_email
DomainDeleteMailer.forced(domain: domain,
registrar: domain.registrar,
registrant: domain.registrant,
template_name: domain.notification_template).deliver_now
end
end
end

View file

@ -0,0 +1,10 @@
module ForceDeleteInteraction
class NotifyRegistrar < Base
def execute
domain.registrar.notifications.create!(text: I18n.t('force_delete_set_on_domain',
domain_name: domain.name,
outzone_date: domain.outzone_date,
purge_date: domain.purge_date))
end
end
end

View file

@ -0,0 +1,17 @@
module ForceDeleteInteraction
class PostSetProcess < Base
def execute
statuses = domain.statuses
# Stop all pending actions
statuses.delete(DomainStatus::PENDING_UPDATE)
statuses.delete(DomainStatus::PENDING_TRANSFER)
statuses.delete(DomainStatus::PENDING_RENEW)
statuses.delete(DomainStatus::PENDING_CREATE)
# Allow deletion
statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED)
statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED)
domain.save(validate: false)
end
end
end

View file

@ -0,0 +1,13 @@
module ForceDeleteInteraction
class PrepareDomain < Base
STATUSES_TO_SET = [DomainStatus::FORCE_DELETE,
DomainStatus::SERVER_RENEW_PROHIBITED,
DomainStatus::SERVER_TRANSFER_PROHIBITED].freeze
def execute
domain.statuses_before_force_delete = domain.statuses
domain.statuses |= STATUSES_TO_SET
domain.save(validate: false)
end
end
end

View file

@ -0,0 +1,12 @@
module ForceDeleteInteraction
class SetForceDelete < Base
def execute
compose(CheckDiscarded, inputs)
compose(PrepareDomain, inputs)
compose(SetStatus, inputs)
compose(PostSetProcess, inputs)
compose(NotifyRegistrar, inputs)
compose(NotifyByEmail, inputs)
end
end
end

View file

@ -0,0 +1,38 @@
module ForceDeleteInteraction
class SetStatus < Base
def execute
domain.force_delete_type = type
type == :fast_track ? force_delete_fast_track : force_delete_soft
domain.save(validate: false)
end
def force_delete_fast_track
domain.force_delete_date = Time.zone.today +
expire_warning_period_days +
redemption_grace_period_days
domain.force_delete_start = Time.zone.today + 1.day
end
def force_delete_soft
years = (domain.valid_to.to_date - Time.zone.today).to_i / 365
soft_forcedelete_dates(years) if years.positive?
end
private
def soft_forcedelete_dates(years)
domain.force_delete_start = domain.valid_to - years.years
domain.force_delete_date = domain.force_delete_start +
Setting.expire_warning_period.days +
Setting.redemption_grace_period.days
end
def redemption_grace_period_days
Setting.redemption_grace_period.days + 1.day
end
def expire_warning_period_days
Setting.expire_warning_period.days
end
end
end

View file

@ -1,22 +0,0 @@
class DomainDeleteConfirmEmailJob < Que::Job
def run(domain_id)
domain = Domain.find(domain_id)
log(domain)
DomainDeleteMailer.confirmation_request(domain: domain,
registrar: domain.registrar,
registrant: domain.registrant).deliver_now
end
private
def log(domain)
message = "Send DomainDeleteMailer#confirm email for domain #{domain.name} (##{domain.id})" \
" to #{domain.registrant.email}"
logger.info(message)
end
def logger
Rails.logger
end
end

View file

@ -1,4 +1,15 @@
class ApplicationMailer < ActionMailer::Base
append_view_path Rails.root.join('app', 'views', 'mailers')
layout 'mailer'
end
def registrant_confirm_url(domain:, method:)
token = domain.registrant_verification_token
base_url = ENV['registrant_portal_verifications_base_url']
url = registrant_domain_delete_confirm_url(domain, token: token) if method == 'delete'
url ||= registrant_domain_update_confirm_url(domain, token: token)
return url if base_url.blank?
"#{base_url}/confirmation/#{domain.name_puny}/#{method}/#{token}"
end
end

View file

@ -2,7 +2,7 @@ class DomainDeleteMailer < ApplicationMailer
def confirmation_request(domain:, registrar:, registrant:)
@domain = DomainPresenter.new(domain: domain, view: view_context)
@registrar = RegistrarPresenter.new(registrar: registrar, view: view_context)
@confirmation_url = confirmation_url(domain)
@confirmation_url = registrant_confirm_url(domain: domain, method: 'delete')
subject = default_i18n_subject(domain_name: domain.name)
mail(to: registrant.email, subject: subject)
@ -48,10 +48,6 @@ class DomainDeleteMailer < ApplicationMailer
private
def confirmation_url(domain)
registrant_domain_delete_confirm_url(domain, token: domain.registrant_verification_token)
end
def forced_email_from
ENV['action_mailer_force_delete_from'] || self.class.default[:from]
end

View file

@ -5,7 +5,7 @@ class RegistrantChangeMailer < ApplicationMailer
@domain = DomainPresenter.new(domain: domain, view: view_context)
@registrar = RegistrarPresenter.new(registrar: registrar, view: view_context)
@new_registrant = RegistrantPresenter.new(registrant: new_registrant, view: view_context)
@confirmation_url = confirmation_url(domain)
@confirmation_url = registrant_confirm_url(domain: domain, method: 'change')
subject = default_i18n_subject(domain_name: domain.name)
mail(to: current_registrant.email, subject: subject)
@ -49,10 +49,6 @@ class RegistrantChangeMailer < ApplicationMailer
private
def confirmation_url(domain)
registrant_domain_update_confirm_url(domain, token: domain.registrant_verification_token)
end
def address_processing
Contact.address_processing?
end

View file

@ -0,0 +1,81 @@
module Actions
class ContactCreate
attr_reader :contact, :legal_document, :ident
def initialize(contact, legal_document, ident)
@contact = contact
@legal_document = legal_document
@ident = ident
end
def call
maybe_remove_address
maybe_attach_legal_doc
validate_ident
commit
end
def maybe_remove_address
return if Contact.address_processing?
contact.city = nil
contact.zip = nil
contact.street = nil
contact.state = nil
contact.country_code = nil
end
def validate_ident
validate_ident_integrity
validate_ident_birthday
identifier = ::Contact::Ident.new(code: ident[:ident], type: ident[:ident_type],
country_code: ident[:ident_country_code])
identifier.validate
contact.identifier = identifier
end
def validate_ident_integrity
return if ident.blank?
if ident[:ident_type].blank?
contact.add_epp_error('2003', nil, 'ident_type',
I18n.t('errors.messages.required_ident_attribute_missing'))
@error = true
elsif !%w[priv org birthday].include?(ident[:ident_type])
contact.add_epp_error('2003', nil, 'ident_type', 'Invalid ident type')
@error = true
end
end
def validate_ident_birthday
return if ident.blank?
return unless ident[:ident_type] != 'birthday' && ident[:ident_country_code].blank?
contact.add_epp_error('2003', nil, 'ident_country_code',
I18n.t('errors.messages.required_ident_attribute_missing'))
@error = true
end
def maybe_attach_legal_doc
return unless legal_document
doc = LegalDocument.create(
documentable_type: Contact,
document_type: legal_document[:type], body: legal_document[:body]
)
contact.legal_documents = [doc]
contact.legal_document_id = doc.id
end
def commit
contact.id = nil # new record
return false if @error
contact.generate_code
contact.save
end
end
end

View file

@ -0,0 +1,41 @@
module Actions
class ContactDelete
attr_reader :contact
attr_reader :new_attributes
attr_reader :legal_document
attr_reader :ident
attr_reader :user
def initialize(contact, legal_document = nil)
@legal_document = legal_document
@contact = contact
end
def call
maybe_attach_legal_doc
if contact.linked?
contact.errors.add(:domains, :exist)
return
end
commit
end
def maybe_attach_legal_doc
return unless legal_document
document = contact.legal_documents.create(
document_type: legal_document[:type],
body: legal_document[:body]
)
contact.legal_document_id = document.id
contact.save
end
def commit
contact.destroy
end
end
end

View file

@ -17,7 +17,7 @@ module Actions
def call
maybe_remove_address
maybe_update_statuses
maybe_update_ident
maybe_update_ident if ident.present?
maybe_attach_legal_doc
commit
end
@ -53,7 +53,11 @@ module Actions
end
def maybe_update_ident
return unless ident[:ident]
unless ident.is_a?(Hash)
contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.valid_ident'))
@error = true
return
end
if contact.identifier.valid?
submitted_ident = ::Contact::Ident.new(code: ident[:ident],

View file

@ -0,0 +1,74 @@
module Actions
class DomainTransfer
attr_reader :domain
attr_reader :transfer_code
attr_reader :legal_document
attr_reader :ident
attr_reader :user
def initialize(domain, transfer_code, user)
@domain = domain
@transfer_code = transfer_code
@user = user
end
def call
return unless domain_exists?
return unless valid_transfer_code?
run_validations
# return domain.pending_transfer if domain.pending_transfer
# attach_legal_document(::Deserializers::Xml::LegalDocument.new(frame).call)
return if domain.errors[:epp_errors].any?
commit
end
def domain_exists?
return true if domain.persisted?
domain.add_epp_error('2303', nil, nil, 'Object does not exist')
false
end
def run_validations
validate_registrar
validate_eligilibty
validate_not_discarded
end
def valid_transfer_code?
return true if transfer_code == domain.transfer_code
domain.add_epp_error('2202', nil, nil, 'Invalid authorization information')
false
end
def validate_registrar
return unless user == domain.registrar
domain.add_epp_error('2002', nil, nil,
I18n.t(:domain_already_belongs_to_the_querying_registrar))
end
def validate_eligilibty
return unless domain.non_transferable?
domain.add_epp_error('2304', nil, nil, 'Object status prohibits operation')
end
def validate_not_discarded
return unless domain.discarded?
domain.add_epp_error('2106', nil, nil, 'Object is not eligible for transfer')
end
def commit
bare_domain = Domain.find(domain.id)
::DomainTransfer.request(bare_domain, user)
end
end
end

View file

@ -52,51 +52,14 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength
force_delete_start + Setting.expire_warning_period.days <= valid_to
end
def schedule_force_delete(type: :fast_track)
if discarded?
raise StandardError, 'Force delete procedure cannot be scheduled while a domain is discarded'
end
type == :fast_track ? force_delete_fast_track : force_delete_soft
end
def add_force_delete_type(force_delete_type)
self.force_delete_type = force_delete_type
end
def force_delete_fast_track
preserve_current_statuses_for_force_delete
add_force_delete_statuses
add_force_delete_type(:fast)
self.force_delete_date = force_delete_fast_track_start_date + 1.day
self.force_delete_start = Time.zone.today + 1.day
stop_all_pending_actions
allow_deletion
save(validate: false)
end
def force_delete_soft
preserve_current_statuses_for_force_delete
add_force_delete_statuses
add_force_delete_type(:soft)
calculate_soft_delete_date
stop_all_pending_actions
allow_deletion
save(validate: false)
end
def clear_force_delete_data
self.force_delete_data = nil
def schedule_force_delete(type: :fast_track, notify_by_email: false)
ForceDeleteInteraction::SetForceDelete.run(domain: self,
type: type,
notify_by_email: notify_by_email)
end
def cancel_force_delete
remove_force_delete_statuses
restore_statuses_before_force_delete
clear_force_delete_data
self.force_delete_date = nil
self.force_delete_start = nil
save(validate: false)
registrar.notifications.create!(text: I18n.t('force_delete_cancelled', domain_name: name))
CancelForceDeleteInteraction::CancelForceDelete.run(domain: self)
end
def outzone_date
@ -107,55 +70,4 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength
(force_delete_date&.beginning_of_day || valid_to + Setting.expire_warning_period.days +
Setting.redemption_grace_period.days)
end
private
def calculate_soft_delete_date
years = (valid_to.to_date - Time.zone.today).to_i / 365
soft_delete_dates(years) if years.positive?
end
def soft_delete_dates(years)
self.force_delete_start = valid_to - years.years
self.force_delete_date = force_delete_start + Setting.expire_warning_period.days +
Setting.redemption_grace_period.days
end
def stop_all_pending_actions
statuses.delete(DomainStatus::PENDING_UPDATE)
statuses.delete(DomainStatus::PENDING_TRANSFER)
statuses.delete(DomainStatus::PENDING_RENEW)
statuses.delete(DomainStatus::PENDING_CREATE)
end
def preserve_current_statuses_for_force_delete
update(statuses_before_force_delete: statuses)
end
def restore_statuses_before_force_delete
self.statuses = statuses_before_force_delete
self.statuses_before_force_delete = nil
end
def add_force_delete_statuses
self.statuses |= [DomainStatus::FORCE_DELETE,
DomainStatus::SERVER_RENEW_PROHIBITED,
DomainStatus::SERVER_TRANSFER_PROHIBITED]
end
def remove_force_delete_statuses
statuses.delete(DomainStatus::FORCE_DELETE)
statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED)
statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED)
statuses.delete(DomainStatus::CLIENT_HOLD)
end
def allow_deletion
statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED)
statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED)
end
def force_delete_fast_track_start_date
Time.zone.today + Setting.expire_warning_period.days + Setting.redemption_grace_period.days
end
end

View file

@ -333,31 +333,6 @@ class Contact < ApplicationRecord
Country.new(country_code)
end
# TODO: refactor, it should not allow to destroy with normal destroy,
# no need separate method
# should use only in transaction
def destroy_and_clean frame
if linked?
errors.add(:domains, :exist)
return false
end
legal_document_data = ::Deserializers::Xml::LegalDocument.new(frame).call
if legal_document_data
doc = LegalDocument.create(
documentable_type: Contact,
document_type: legal_document_data[:type],
body: legal_document_data[:body]
)
self.legal_documents = [doc]
self.legal_document_id = doc.id
self.save
end
destroy
end
def to_upcase_country_code
self.ident_country_code = ident_country_code.upcase if ident_country_code
self.country_code = country_code.upcase if country_code

View file

@ -418,7 +418,7 @@ class Domain < ApplicationRecord
pending_delete_confirmation!
save(validate: false) # should check if this did succeed
DomainDeleteConfirmEmailJob.enqueue(id)
DomainDeleteConfirmInteraction::SendRequest.run(domain: self)
end
def cancel_pending_delete

View file

@ -30,12 +30,13 @@ class Epp::Contact < Contact
at
end
def new(frame, registrar)
def new(frame, registrar, epp: true)
return super if frame.blank?
attrs = epp ? attrs_from(frame, new_record: true) : frame
super(
attrs_from(frame, new_record: true).merge(
code: frame.css('id').text,
attrs.merge(
code: epp ? frame.css('id').text : frame[:id],
registrar: registrar
)
)

View file

@ -137,7 +137,8 @@ class Registrar < ApplicationRecord
def api_ip_white?(ip)
return true unless Setting.api_ip_whitelist_enabled
white_ips.api.pluck(:ipv4, :ipv6).flatten.include?(ip)
white_ips.api.include_ip?(ip)
end
# Audit log is needed, therefore no raw SQL

View file

@ -2,8 +2,8 @@ class WhiteIp < ApplicationRecord
include Versions
belongs_to :registrar
validates :ipv4, format: { with: /\A(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\z/, allow_blank: true }
validates :ipv6, format: { with: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/, allow_blank: true }
validate :valid_ipv4?
validate :valid_ipv6?
validate :validate_ipv4_and_ipv6
def validate_ipv4_and_ipv6
@ -11,6 +11,22 @@ class WhiteIp < ApplicationRecord
errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present))
end
def valid_ipv4?
return if ipv4.blank?
IPAddr.new(ipv4, Socket::AF_INET)
rescue StandardError => _e
errors.add(:ipv4, :invalid)
end
def valid_ipv6?
return if ipv6.blank?
IPAddr.new(ipv6, Socket::AF_INET6)
rescue StandardError => _e
errors.add(:ipv6, :invalid)
end
API = 'api'
REGISTRAR = 'registrar'
INTERFACES = [API, REGISTRAR]
@ -23,8 +39,37 @@ class WhiteIp < ApplicationRecord
end
class << self
# rubocop:disable Style/CaseEquality
# rubocop:disable Metrics/AbcSize
def include_ip?(ip)
where('ipv4 = :ip OR ipv6 = :ip', ip: ip).any?
return false if ip.blank?
where(id: ids_including(ip)).any?
end
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) }
end
if check_ip6(ip).present?
ipv6 = select { |white_ip| IPAddr.new(white_ip.ipv6, Socket::AF_INET6) === check_ip6(ip) }
end
(ipv4 + ipv6).pluck(:id).flatten.uniq
end
# rubocop:enable Style/CaseEquality
# rubocop:enable Metrics/AbcSize
def check_ip4(ip)
IPAddr.new(ip, Socket::AF_INET)
rescue StandardError => _e
nil
end
def check_ip6(ip)
IPAddr.new(ip, Socket::AF_INET6)
rescue StandardError => _e
nil
end
end
end

View file

@ -5,6 +5,7 @@ class RegistrantChange
end
def confirm
Dispute.close_by_domain(@domain.name) if @domain.disputed?
notify_registrant
end

View file

@ -14,11 +14,15 @@ xml.epp_head do
end
xml.tag!('contact:postalInfo', type: 'int') do
xml.tag!('contact:name', @contact.name)
if can? :view_full_info, @contact, @password
xml.tag!('contact:name', @contact.name)
else
xml.tag!('contact:name', 'No access')
end
if can? :view_full_info, @contact, @password
xml.tag!('contact:org', @contact.org_name) if @contact.org_name.present?
if address_processing?
if Contact.address_processing?
xml.tag!('contact:addr') do
xml.tag!('contact:street', @contact.street)
xml.tag!('contact:city', @contact.city)
@ -31,7 +35,7 @@ xml.epp_head do
else
xml.tag!('contact:org', 'No access')
if address_processing?
if Contact.address_processing?
xml.tag!('contact:addr') do
xml.tag!('contact:street', 'No access')
xml.tag!('contact:city', 'No access')

View file

@ -2,7 +2,7 @@
<p>Lugupeetud .ee domeeni registreerija/halduskontakt</p>
<p>Domeeninimi <%= @domain.name %> on aegunud ja ei ole alates <%= @domain.on_hold_date %> internetis kättesaadav. Domeeniga on seotud puudulike kontakti objekte, milles tulenevalt on Eesti Interneti SA blokeerinud domeeni pikendamise ja registripidaja vahetuse, kuniks kontaktandmed korrastatakse. Andmete korrastamiseks ja registreeringu pikendamiseks pöörduge palun oma registripidaja poole.</p>
<p>Domeeninimi <%= @domain.name %> on aegunud ja ei ole alates <%= @domain.on_hold_date %> internetis kättesaadav. Domeeniga on seotud puudulikke kontakti objekte, millest tulenevalt on Eesti Interneti SA blokeerinud domeeni pikendamise ja registripidaja vahetuse, kuniks kontaktandmed korrastatakse. Andmete korrastamiseks ja registreeringu pikendamiseks pöörduge palun oma registripidaja poole.</p>
<p><%= @domain.name %> pikendamata jätmisel domeen kustub ja läheb <%= @domain.delete_date %> oksjonile .ee oksjonikeskkonda. Domeenioksjonite kohta loe lähemalt <a href="https://www.internet.ee/domeenioksjonid">siit</a>.</p>

View file

@ -2,7 +2,7 @@ Domeen <%= @domain.name %> on aegunud ning suunatud kustutusmenetlusse kuna olem
Lugupeetud .ee domeeni registreerija/halduskontakt
Domeeninimi <%= @domain.name %> on aegunud ja ei ole alates <%= @domain.on_hold_date %> internetis kättesaadav. Domeeniga on seotud puudulike kontakti objekte, milles tulenevalt on Eesti Interneti SA blokeerinud domeeni pikendamise ja registripidaja vahetuse, kuniks kontaktandmed korrastatakse. Andmete korrastamiseks ja registreeringu pikendamiseks pöörduge palun oma registripidaja poole.
Domeeninimi <%= @domain.name %> on aegunud ja ei ole alates <%= @domain.on_hold_date %> internetis kättesaadav. Domeeniga on seotud puudulikke kontakti objekte, millest tulenevalt on Eesti Interneti SA blokeerinud domeeni pikendamise ja registripidaja vahetuse, kuniks kontaktandmed korrastatakse. Andmete korrastamiseks ja registreeringu pikendamiseks pöörduge palun oma registripidaja poole.
<%= @domain.name %> pikendamata jätmisel domeen kustub ja läheb <%= @domain.delete_date %> oksjonile .ee oksjonikeskkonda. Domeenioksjonite kohta loe lähemalt siit https://www.internet.ee/domeenioksjonid.

View file

@ -5,7 +5,7 @@
.col-md-8
= render 'registrar/contacts/form/general', f: f
- if address_processing?
- if Contact.address_processing?
.row
.col-md-8
= render 'registrar/contacts/form/address', f: f

View file

@ -13,5 +13,5 @@
- registrant = Contact.find_by_code(x.text)
%tr
%td= x['type']
%td= registrant.name
%td= registrant.registrar == current_registrar_user.registrar ? registrant.name : 'N/A'
%td= x.text

View file

@ -23,7 +23,7 @@
<% registrant = Contact.find_by_code(@data.css('registrant').text) %>
<dt><%= t('.registrant') %></dt>
<dd><%= "#{registrant.name} (#{@data.css('registrant').text})" %></dd>
<dd><%= registrant.registrar == current_registrar_user.registrar ? "#{registrant.name} (#{@data.css('registrant').text})" : @data.css('registrant').text %></dd>
<dt><%= t('.registered') %></dt>
<dd><%= @data.css('crDate').text %></dd>

View file

@ -36,6 +36,7 @@ module DomainNameRegistry
# Autoload all model subdirs
config.autoload_paths += Dir[Rails.root.join('app', 'models', '**/')]
config.autoload_paths += Dir[Rails.root.join('app', 'interactions', '**/')]
config.eager_load_paths << config.root.join('lib', 'validators')
config.watchable_dirs['lib'] = %i[rb]

View file

@ -87,6 +87,9 @@ 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
# Base URL (inc. https://) of REST registrant portal
# Leave blank to use internal registrant portal
registrant_portal_verifications_base_url: ''
#
# MISC

View file

@ -37,12 +37,35 @@ Rails.application.routes.draw do
get 'error/:command', to: 'errors#error'
end
mount Repp::API => '/'
namespace :repp do
namespace :v1 do
resources :contacts do
collection do
get 'check/:id', to: 'contacts#check'
end
end
resources :accounts do
collection do
get 'balance'
end
end
resources :auctions, only: %i[index]
resources :retained_domains, only: %i[index]
namespace :registrar do
resources :nameservers do
collection do
put '/', to: 'nameservers#update'
end
end
end
resources :domains do
collection do
get ':id/transfer_info', to: 'domains#transfer_info', constraints: { id: /.*/ }
post 'transfer', to: 'domains#transfer'
patch 'contacts', to: 'domains/contacts#update'
end
end
end
end
@ -56,6 +79,8 @@ Rails.application.routes.draw do
namespace :v1 do
namespace :registrant do
post 'auth/eid', to: 'auth#eid'
get 'confirms/:name/:template/:token', to: 'confirms#index', constraints: { name: /[^\/]+/ }
post 'confirms/:name/:template/:token/:decision', to: 'confirms#update', constraints: { name: /[^\/]+/ }
resources :domains, only: %i[index show], param: :uuid do
resource :registry_lock, only: %i[create destroy]

View file

@ -19,7 +19,11 @@ Content-Length: 37
Content-Type: application/json
{
"balance": "324.45",
"currency": "EUR"
"code": 1000,
"message": "Command completed successfully",
"data": {
"balance": "356.0",
"currency": "EUR"
}
}
```

View file

@ -88,3 +88,141 @@ Content-Type: application/json
"total_number_of_records": 2
}
```
## POST /repp/v1/contacts
Creates new contact
#### Request
```
POST /repp/v1/contacts HTTP/1.1
Authorization: Basic dGVzdDp0ZXN0MTIz
Content-Type: application/json
{
"contact": {
"name": "John Doe",
"email": "john@doe.com",
"phone": "+371.1234567",
"ident": {
"ident": "12345678901",
"ident_type": "priv",
"ident_country_code": "EE"
}
}
}
```
#### Response
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
{
"code": 1000,
"message": "Command completed successfully",
"data": {
"contact": {
"id": "ATSAA:20DCDCA1"
}
}
}
```
#### Failed response
```
HTTP/1.1 400
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
{
"code": 2005,
"message": "Ident code does not conform to national identification number format of Estonia",
"data": {}
}
```
## PUT /repp/v1/contacts/**contact id**
Updates existing contact
#### Request
```
PUT /repp/v1/contacts/ATSAA:9CD5F321 HTTP/1.1
Authorization: Basic dGVzdDp0ZXN0MTIz
Content-Type: application/json
{
"contact": {
"phone": "+372.123123123"
}
}
```
#### Response
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
{
"code": 1000,
"message": "Command completed successfully",
"data": {
"contact": {
"id": "ATSAA:20DCDCA1"
}
}
}
```
#### Failed response
```
HTTP/1.1 400
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
{
"code": 2005,
"message": "Phone nr is invalid [phone]",
"data": {}
}
```
## DELETE /repp/v1/contacts/**contact id**
Deletes existing contact
#### Request
```
DELETE /repp/v1/contacts/ATSAA:9CD5F321 HTTP/1.1
Authorization: Basic dGVzdDp0ZXN0MTIz
Content-Type: application/json
```
#### Response
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
{
"code": 1000,
"message": "Command completed successfully",
"data": {}
}
```
#### Failed response
```
HTTP/1.1 400
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
{
"code": 2305,
"message": "Object association prohibits operation [domains]",
"data": {}
}
```

View file

@ -25,44 +25,52 @@ Content-Type: application/json
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 808
Content-Type: application/json
{
"domains": [
{
"id": 1,
"name": "domain0.ee",
"registrar_id": 2,
"registered_at": "2015-09-09T09:11:14.861Z",
"status": null,
"valid_from": "2015-09-09T09:11:14.861Z",
"valid_to": "2016-09-09T09:11:14.861Z",
"registrant_id": 1,
"transfer_code": "98oiewslkfkd",
"created_at": "2015-09-09T09:11:14.861Z",
"updated_at": "2015-09-09T09:11:14.860Z",
"name_dirty": "domain0.ee",
"name_puny": "domain0.ee",
"period": 1,
"period_unit": "y",
"creator_str": null,
"updator_str": null,
"outzone_at": "2016-09-24T09:11:14.861Z",
"delete_date": "2016-10-24",
"registrant_verification_asked_at": null,
"registrant_verification_token": null,
"pending_json": {
},
"force_delete_date": null,
"statuses": [
"ok"
],
"status_notes": {
"code": 1000,
"message": "Command completed successfully",
"data": {
"domains": [
{
"id": 7,
"name": "private.ee",
"registrar_id": 2,
"valid_to": "2022-09-23T00:00:00.000+03:00",
"registrant_id": 11,
"created_at": "2020-09-22T14:16:47.420+03:00",
"updated_at": "2020-10-21T13:31:43.733+03:00",
"name_dirty": "private.ee",
"name_puny": "private.ee",
"period": 1,
"period_unit": "y",
"creator_str": "2-ApiUser: test",
"updator_str": null,
"outzone_at": null,
"delete_date": null,
"registrant_verification_asked_at": null,
"registrant_verification_token": null,
"pending_json": {},
"force_delete_date": null,
"statuses": [
"serverRenewProhibited"
],
"status_notes": {
"ok": "",
"serverRenewProhibited": ""
},
"upid": null,
"up_date": null,
"uuid": "6b6affa7-1449-4bd8-acf5-8b4752406705",
"locked_by_registrant_at": null,
"force_delete_start": null,
"force_delete_data": null,
"auth_info": "367b1e6d1f0d9aa190971ad8f571cd4d",
"valid_from": "2020-09-22T14:16:47.420+03:00"
}
}
],
"total_number_of_records": 2
],
"total_number_of_records": 10
}
}
```
@ -83,14 +91,17 @@ Content-Type: application/json
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 54
Content-Type: application/json
{
"domains": [
"domain1.ee"
],
"total_number_of_records": 2
"code": 1000,
"message": "Command completed successfully",
"data": {
"domains": [
"private.ee",
],
"total_number_of_records": 1
}
}
```
@ -117,65 +128,68 @@ Please note that domain transfer/authorisation code must be placed in header - *
```
HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 784
Content-Type: application/json
{
"domain":"ee-test.ee",
"registrant":{
"code":"EE:R1",
"name":"Registrant",
"ident":"17612535",
"ident_type":"org",
"ident_country_code":"EE",
"phone":"+372.1234567",
"email":"registrant@cache.ee",
"street":"Businesstreet 1",
"city":"Tallinn",
"zip":"10101",
"country_code":"EE",
"statuses":[
"ok",
"linked"
]
},
"admin_contacts":[
{
"code":"EE:A1",
"name":"Admin Contact",
"ident":"17612535376",
"ident_type":"priv",
"ident_country_code":"EE",
"phone":"+372.7654321",
"email":"admin@cache.ee",
"street":"Adminstreet 2",
"city":"Tallinn",
"zip":"12345",
"country_code":"EE",
"statuses":[
"ok",
"linked"
"code": 1000,
"message": "Command completed successfully",
"data": {
"domain":"ee-test.ee",
"registrant":{
"code":"EE:R1",
"name":"Registrant",
"ident":"17612535",
"ident_type":"org",
"ident_country_code":"EE",
"phone":"+372.1234567",
"email":"registrant@cache.ee",
"street":"Businesstreet 1",
"city":"Tallinn",
"zip":"10101",
"country_code":"EE",
"statuses":[
"ok",
"linked"
]
},
"admin_contacts":[
{
"code":"EE:A1",
"name":"Admin Contact",
"ident":"17612535376",
"ident_type":"priv",
"ident_country_code":"EE",
"phone":"+372.7654321",
"email":"admin@cache.ee",
"street":"Adminstreet 2",
"city":"Tallinn",
"zip":"12345",
"country_code":"EE",
"statuses":[
"ok",
"linked"
]
}
],
"tech_contacts":[
{
"code":"EE:T1",
"name":"Tech Contact",
"ident":"17612536",
"ident_type":"org",
"ident_country_code":"EE",
"phone":"+372.7654321",
"email":"tech@cache.ee",
"street":"Techstreet 1",
"city":"Tallinn",
"zip":"12345",
"country_code":"EE",
"statuses":[
"ok",
"linked"
]
}
]
}
],
"tech_contacts":[
{
"code":"EE:T1",
"name":"Tech Contact",
"ident":"17612536",
"ident_type":"org",
"ident_country_code":"EE",
"phone":"+372.7654321",
"email":"tech@cache.ee",
"street":"Techstreet 1",
"city":"Tallinn",
"zip":"12345",
"country_code":"EE",
"statuses":[
"ok",
"linked"
]
}
]
}
}
```

View file

@ -5,15 +5,26 @@ Replaces all domain contacts of the current registrar.
### Example request
```
$ curl https://repp.internet.ee/v1/domains/contacts \
-X PATCH \
-u username:password \
-d current_contact_id=foo \
-d new_contact_id=bar
PATCH /repp/v1/domains/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
```
{
"affected_domains": ["example.com", "example.org"]
"code": 1000,
"message": "Command completed successfully",
"data": {
"affected_domains": [
"private.ee",
],
"skipped_domains": []
}
}
```

View file

@ -1,28 +1,28 @@
# Domain transfers
## POST /repp/v1/domain_transfers
## POST /repp/v1/domains/transfer
Transfers domains.
#### Request
```
POST /repp/v1/domain_transfers
POST /repp/v1/domains/transfer
Accept: application/json
Content-Type: application/json
Authorization: Basic dGVzdDp0ZXN0dGVzdA==
{
"data":{
"domainTransfers":[
{
"domainName":"example.com",
"transferCode":"63e7"
},
{
"domainName":"example.org",
"transferCode":"15f9"
}
]
}
"data": {
"domain_transfers": [
{
"domain_name":"example.com",
"transferCode":"63e7"
},
{
"domain_name":"example.org",
"transferCode":"15f9"
}
]
}
}
```
@ -31,14 +31,21 @@ Authorization: Basic dGVzdDp0ZXN0dGVzdA==
HTTP/1.1 200
Content-Type: application/json
{
"data":[
"code": 1000,
"message": "Command completed successfully",
"data": {
"success": [
{
"type":"domain_transfer"
"type": "domain_transfer",
"domain_name": "example.com"
},
{
"type":"domain_transfer"
"type": "domain_transfer",
"domain_name": "example.org"
}
]
],
"failed": []
}
}
```
@ -48,13 +55,32 @@ Content-Type: application/json
HTTP/1.1 400
Content-Type: application/json
{
"errors":[
"code": 1000,
"message": "Command completed successfully",
"data": {
"success": [],
"failed": [
{
"title":"example.com transfer code is wrong"
"type": "domain_transfer",
"domain_name": "example.com",
"errors": [
{
"code": "2202",
"msg": "Invalid authorization information"
}
]
},
{
"title":"example.org does not exist"
"type": "domain_transfer",
"domain_name": "example.org",
"errors": [
{
"code": "2304",
"msg": "Object status prohibits operation"
}
]
}
]
]
}
}
```

View file

@ -10,15 +10,15 @@ Accept: application/json
Content-Type: application/json
Authorization: Basic dGVzdDp0ZXN0dGVzdA==
{
"data":{
"type": "nameserver",
"id": "ns1.example.com",
"attributes": {
"hostname": "new-ns1.example.com",
"ipv4": ["192.0.2.1", "192.0.2.2"],
"ipv6": ["2001:db8::1", "2001:db8::2"]
},
"data": {
"type": "nameserver",
"id": "ns1.example.com",
"attributes": {
"hostname": "new-ns1.example.com",
"ipv4": ["192.0.2.1", "192.0.2.2"],
"ipv6": ["2001:db8::1", "2001:db8::2"]
}
}
}
```
@ -27,16 +27,26 @@ Authorization: Basic dGVzdDp0ZXN0dGVzdA==
HTTP/1.1 200
Content-Type: application/json
{
"data":{
"code": 1000,
"message": "Command completed successfully",
"data": {
"type": "nameserver",
"id": "new-ns1.example.com",
"attributes": {
"hostname": "new-ns1.example.com",
"ipv4": ["192.0.2.1", "192.0.2.2"],
"ipv6": ["2001:db8::1", "2001:db8::2"]
}
},
"affected_domains": ["example.com", "example.org"]
"ipv4": [
"192.0.2.1",
"192.0.2.2"
],
"ipv6": [
"2001:db8::1",
"2001:db8::2"
]
},
"affected_domains": [
"private.ee"
]
}
}
```
@ -44,14 +54,10 @@ Content-Type: application/json
```
HTTP/1.1 400
Content-Type: application/json
{
"errors":[
{
"title":"ns1.example.com does not exist"
},
{
"title":"192.0.2.1 is not a valid IPv4 address"
}
]
"code": 2005,
"message": "IPv4 is invalid [ipv4]",
"data": {}
}
```

View file

@ -0,0 +1,10 @@
require 'deserializers/xml/legal_document'
require 'deserializers/xml/ident'
require 'deserializers/xml/contact'
module Deserializers
module Xml
class ContactCreate < ContactUpdate
end
end
end

BIN
lib/serializers/registrant_api/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,36 @@
module Serializers
module Repp
class Contact
attr_reader :contact
def initialize(contact, show_address:)
@contact = contact
@show_address = show_address
end
def to_json(obj = contact)
json = { id: obj.code, name: obj.name, ident: ident,
email: obj.email, phone: obj.phone, fax: obj.fax,
auth_info: obj.auth_info, statuses: obj.statuses,
disclosed_attributes: obj.disclosed_attributes }
json[:address] = address if @show_address
json
end
def ident
{
code: contact.ident,
type: contact.ident_type,
country_code: contact.ident_country_code,
}
end
def address
{ street: contact.street, zip: contact.zip, city: contact.city,
state: contact.state, country_code: contact.country_code }
end
end
end
end

View file

@ -27,8 +27,8 @@ class APIDomainContactsTest < ApplicationIntegrationTest
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response :ok
assert_equal ({ affected_domains: %w[airport.test shop.test],
skipped_domains: [] }),
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
@ -42,7 +42,7 @@ class APIDomainContactsTest < ApplicationIntegrationTest
assert_response :ok
assert_equal %w[airport.test shop.test], JSON.parse(response.body,
symbolize_names: true)[:skipped_domains]
symbolize_names: true)[:data][:skipped_domains]
end
def test_keep_other_tech_contacts_intact
@ -66,10 +66,8 @@ class APIDomainContactsTest < ApplicationIntegrationTest
new_contact_id: 'william-002' },
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response :bad_request
assert_equal ({ error: { type: 'invalid_request_error',
param: 'current_contact_id',
message: 'No such contact: jack-001' } }),
assert_response :not_found
assert_equal ({ code: 2303, message: 'Object does not exist' }),
JSON.parse(response.body, symbolize_names: true)
end
@ -77,10 +75,8 @@ class APIDomainContactsTest < ApplicationIntegrationTest
patch '/repp/v1/domains/contacts', params: { current_contact_id: 'non-existent',
new_contact_id: 'john-001' },
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response :bad_request
assert_equal ({ error: { type: 'invalid_request_error',
param: 'current_contact_id',
message: 'No such contact: non-existent' } }),
assert_response :not_found
assert_equal ({ code: 2303, message: 'Object does not exist' }),
JSON.parse(response.body, symbolize_names: true)
end
@ -88,10 +84,8 @@ class APIDomainContactsTest < ApplicationIntegrationTest
patch '/repp/v1/domains/contacts', params: { current_contact_id: 'william-001',
new_contact_id: 'non-existent' },
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response :bad_request
assert_equal ({ error: { type: 'invalid_request_error',
param: 'new_contact_id',
message: 'No such contact: non-existent' } }),
assert_response :not_found
assert_equal ({code: 2303, message: 'Object does not exist'}),
JSON.parse(response.body, symbolize_names: true)
end
@ -100,9 +94,7 @@ class APIDomainContactsTest < ApplicationIntegrationTest
new_contact_id: 'invalid' },
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response :bad_request
assert_equal ({ error: { type: 'invalid_request_error',
param: 'new_contact_id',
message: 'New contact must be valid' } }),
assert_equal ({ code: 2304, message: 'New contact must be valid', data: {} }),
JSON.parse(response.body, symbolize_names: true)
end
@ -111,8 +103,7 @@ class APIDomainContactsTest < ApplicationIntegrationTest
new_contact_id: 'william-001' },
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response :bad_request
assert_equal ({ error: { type: 'invalid_request_error',
message: 'New contact ID must be different from current contact ID' } }),
assert_equal ({ code: 2304, message: 'New contact must be different from current', data: {} }),
JSON.parse(response.body, symbolize_names: true)
end

View file

@ -12,34 +12,21 @@ class APIDomainTransfersTest < ApplicationIntegrationTest
Setting.transfer_wait_time = @original_transfer_wait_time
end
def test_returns_domain_transfers
post '/repp/v1/domain_transfers', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response 200
assert_equal ({ data: [{
type: 'domain_transfer',
attributes: {
domain_name: 'shop.test'
},
}] }),
JSON.parse(response.body, symbolize_names: true)
end
def test_creates_new_domain_transfer
assert_difference -> { @domain.transfers.size } do
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
end
end
def test_approves_automatically_if_auto_approval_is_enabled
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert @domain.transfers.last.approved?
end
def test_assigns_new_registrar
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
@domain.reload
assert_equal @new_registrar, @domain.registrar
@ -48,7 +35,7 @@ class APIDomainTransfersTest < ApplicationIntegrationTest
def test_regenerates_transfer_code
@old_transfer_code = @domain.transfer_code
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
@domain.reload
refute_equal @domain.transfer_code, @old_transfer_code
@ -58,51 +45,28 @@ class APIDomainTransfersTest < ApplicationIntegrationTest
@old_registrar = @domain.registrar
assert_difference -> { @old_registrar.notifications.count } do
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
end
end
def test_duplicates_registrant_admin_and_tech_contacts
assert_difference -> { @new_registrar.contacts.size }, 3 do
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
end
end
def test_reuses_identical_contact
post '/repp/v1/domain_transfers', params: request_params, as: :json,
post '/repp/v1/domains/transfer', params: request_params, as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_equal 1, @new_registrar.contacts.where(name: 'William').size
end
def test_fails_if_domain_does_not_exist
post '/repp/v1/domain_transfers',
params: { data: { domainTransfers: [{ domainName: 'non-existent.test',
transferCode: 'any' }] } },
as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response 400
assert_equal ({ errors: [{ title: 'non-existent.test does not exist' }] }),
JSON.parse(response.body, symbolize_names: true)
end
def test_fails_if_transfer_code_is_wrong
post '/repp/v1/domain_transfers',
params: { data: { domainTransfers: [{ domainName: 'shop.test',
transferCode: 'wrong' }] } },
as: :json,
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response 400
refute_equal @new_registrar, @domain.registrar
assert_equal ({ errors: [{ title: 'shop.test transfer code is wrong' }] }),
JSON.parse(response.body, symbolize_names: true)
end
private
def request_params
{ data: { domainTransfers: [{ domainName: 'shop.test', transferCode: '65078d5' }] } }
{ data: { domain_transfers: [{ domain_name: 'shop.test', transfer_code: '65078d5' }] } }
end
def http_auth_key

View file

@ -60,12 +60,14 @@ class APINameserversPutTest < ApplicationIntegrationTest
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response 200
assert_equal ({ data: { type: 'nameserver',
assert_equal ({ code: 1000,
message: 'Command completed successfully',
data: { type: 'nameserver',
id: 'ns55.bestnames.test',
attributes: { hostname: 'ns55.bestnames.test',
ipv4: ['192.0.2.55'],
ipv6: ['2001:db8::55'] } },
affected_domains: ["airport.test", "shop.test"] }),
ipv6: ['2001:db8::55'] },
affected_domains: ["airport.test", "shop.test"] }}),
JSON.parse(response.body, symbolize_names: true)
end
@ -85,7 +87,7 @@ class APINameserversPutTest < ApplicationIntegrationTest
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response 404
assert_equal ({ errors: [{ title: 'Hostname non-existent.test does not exist' }] }),
assert_equal ({code: 2303, message: 'Object does not exist' }),
JSON.parse(response.body, symbolize_names: true)
end
@ -96,7 +98,8 @@ class APINameserversPutTest < ApplicationIntegrationTest
headers: { 'HTTP_AUTHORIZATION' => http_auth_key }
assert_response 400
assert_equal ({ errors: [{ title: 'Hostname is missing' }] }),
assert_equal ({ code: 2003,
message: 'param is missing or the value is empty: hostname' }),
JSON.parse(response.body, symbolize_names: true)
end

View file

@ -0,0 +1,297 @@
require 'test_helper'
require 'auth_token/auth_token_creator'
class RegistrantApiVerificationsTest < ApplicationIntegrationTest
def setup
super
@domain = domains(:hospital)
@registrant = @domain.registrant
@new_registrant = contacts(:jack)
@user = users(:api_bestnames)
@token = 'verysecrettoken'
@domain.update!(statuses: [DomainStatus::PENDING_UPDATE],
registrant_verification_asked_at: Time.zone.now - 1.day,
registrant_verification_token: @token)
end
def test_fetches_registrant_change_request
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
assert @domain.registrant_update_confirmable?(@token)
get "/api/v1/registrant/confirms/#{@domain.name_puny}/change/#{@token}"
assert_equal(200, response.status)
res = JSON.parse(response.body, symbolize_names: true)
expected_body = {
domain_name: "hospital.test",
current_registrant: {
name: @registrant.name,
ident: @registrant.ident,
country: @registrant.ident_country_code
},
new_registrant: {
name: @new_registrant.name,
ident: @new_registrant.ident,
country: @new_registrant.ident_country_code
}
}
assert_equal expected_body, res
end
def test_approves_registrant_change_request
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
assert @domain.registrant_update_confirmable?(@token)
perform_enqueued_jobs do
post "/api/v1/registrant/confirms/#{@domain.name_puny}/change/#{@token}/confirmed"
assert_equal(200, response.status)
res = JSON.parse(response.body, symbolize_names: true)
expected_body = {
domain_name: @domain.name,
current_registrant: {
name: @new_registrant.name,
ident: @new_registrant.ident,
country: @new_registrant.ident_country_code
},
status: 'confirmed'
}
assert_equal expected_body, res
end
end
def test_rejects_registrant_change_request
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
assert @domain.registrant_update_confirmable?(@token)
post "/api/v1/registrant/confirms/#{@domain.name_puny}/change/#{@token}/rejected"
assert_equal(200, response.status)
res = JSON.parse(response.body, symbolize_names: true)
expected_body = {
domain_name: @domain.name,
current_registrant: {
name: @registrant.name,
ident: @registrant.ident,
country: @registrant.ident_country_code
},
status: 'rejected'
}
assert_equal expected_body, res
end
def test_registrant_change_requires_valid_attributes
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
get "/api/v1/registrant/confirms/#{@domain.name_puny}/change/123"
assert_equal 401, response.status
get "/api/v1/registrant/confirms/aohldfjg.ee/change/123"
assert_equal 404, response.status
post "/api/v1/registrant/confirms/#{@domain.name_puny}/change/#{@token}/invalidaction"
assert_equal 404, response.status
end
def test_fetches_domain_delete_request
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, statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION])
@domain.reload
assert @domain.registrant_delete_confirmable?(@token)
get "/api/v1/registrant/confirms/#{@domain.name_puny}/delete/#{@token}"
assert_equal(200, response.status)
res = JSON.parse(response.body, symbolize_names: true)
expected_body = {
domain_name: "hospital.test",
current_registrant: {
name: @registrant.name,
ident: @registrant.ident,
country: @registrant.ident_country_code
}
}
assert_equal expected_body, res
end
def test_approves_domain_delete_request
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, statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION])
@domain.reload
assert @domain.registrant_delete_confirmable?(@token)
post "/api/v1/registrant/confirms/#{@domain.name_puny}/delete/#{@token}/confirmed"
assert_equal(200, response.status)
res = JSON.parse(response.body, symbolize_names: true)
expected_body = {
domain_name: @domain.name,
current_registrant: {
name: @registrant.name,
ident: @registrant.ident,
country: @registrant.ident_country_code
},
status: 'confirmed'
}
assert_equal expected_body, res
end
def test_rejects_domain_delete_request
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, statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION])
@domain.reload
assert @domain.registrant_delete_confirmable?(@token)
post "/api/v1/registrant/confirms/#{@domain.name_puny}/delete/#{@token}/rejected"
assert_equal(200, response.status)
res = JSON.parse(response.body, symbolize_names: true)
expected_body = {
domain_name: @domain.name,
current_registrant: {
name: @registrant.name,
ident: @registrant.ident,
country: @registrant.ident_country_code
},
status: 'rejected'
}
assert_equal expected_body, res
end
def test_domain_delete_requires_valid_attributes
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, statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION])
@domain.reload
get "/api/v1/registrant/confirms/#{@domain.name_puny}/delete/123"
assert_equal 401, response.status
get "/api/v1/registrant/confirms/aohldfjg.ee/delete/123"
assert_equal 404, response.status
post "/api/v1/registrant/confirms/#{@domain.name_puny}/delete/#{@token}/invalidaction"
assert_equal 404, response.status
end
#def test_get_non_existent_domain_details_by_uuid
# get '/api/v1/registrant/domains/random-uuid', headers: @auth_headers
# assert_equal(404, response.status)
# response_json = JSON.parse(response.body, symbolize_names: true)
# assert_equal({ errors: [base: ['Domain not found']] }, response_json)
#end
#def test_root_returns_domain_list
# get '/api/v1/registrant/domains', headers: @auth_headers
# assert_equal(200, response.status)
# response_json = JSON.parse(response.body, symbolize_names: true)
# array_of_domain_names = response_json.map { |x| x[:name] }
# assert(array_of_domain_names.include?('hospital.test'))
# array_of_domain_registrars = response_json.map { |x| x[:registrar] }
# assert(array_of_domain_registrars.include?({name: 'Good Names', website: nil}))
#end
#def test_root_accepts_limit_and_offset_parameters
# get '/api/v1/registrant/domains', params: { 'limit' => 2, 'offset' => 0 },
# headers: @auth_headers
# response_json = JSON.parse(response.body, symbolize_names: true)
# assert_equal(200, response.status)
# assert_equal(2, response_json.count)
# get '/api/v1/registrant/domains', headers: @auth_headers
# response_json = JSON.parse(response.body, symbolize_names: true)
# assert_equal(4, response_json.count)
#end
#def test_root_does_not_accept_limit_higher_than_200
# get '/api/v1/registrant/domains', params: { 'limit' => 400, 'offset' => 0 },
# headers: @auth_headers
# assert_equal(400, response.status)
# response_json = JSON.parse(response.body, symbolize_names: true)
# assert_equal({ errors: [{ limit: ['parameter is out of range'] }] }, response_json)
#end
#def test_root_does_not_accept_offset_lower_than_0
# get '/api/v1/registrant/domains', params: { 'limit' => 200, 'offset' => "-10" },
# headers: @auth_headers
# assert_equal(400, response.status)
# response_json = JSON.parse(response.body, symbolize_names: true)
# assert_equal({ errors: [{ offset: ['parameter is out of range'] }] }, response_json)
#end
#def test_root_returns_401_without_authorization
# get '/api/v1/registrant/domains'
# assert_equal(401, response.status)
# json_body = JSON.parse(response.body, symbolize_names: true)
# assert_equal({ errors: [base: ['Not authorized']] }, json_body)
#end
#def test_details_returns_401_without_authorization
# get '/api/v1/registrant/domains/5edda1a5-3548-41ee-8b65-6d60daf85a37'
# assert_equal(401, response.status)
# json_body = JSON.parse(response.body, symbolize_names: true)
# assert_equal({ errors: [base: ['Not authorized']] }, json_body)
#end
end

View file

@ -44,7 +44,7 @@ class EppContactInfoBaseTest < EppTestCase
contact: xml_schema).text
end
def test_hides_password_when_current_registrar_is_not_sponsoring
def test_hides_password_and_name_when_current_registrar_is_not_sponsoring
non_sponsoring_registrar = registrars(:goodnames)
@contact.update!(registrar: non_sponsoring_registrar)
@ -70,6 +70,7 @@ class EppContactInfoBaseTest < EppTestCase
assert_epp_response :completed_successfully
response_xml = Nokogiri::XML(response.body)
assert_nil response_xml.at_xpath('//contact:authInfo', contact: xml_schema)
assert_equal 'No access', response_xml.at_xpath('//contact:name', contact: xml_schema).text
end
private

View file

@ -35,7 +35,6 @@ class EppDomainDeleteBaseTest < EppTestCase
XML
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
# binding.pry
assert_includes Domain.find_by(name: 'invalid.test').statuses, DomainStatus::PENDING_DELETE_CONFIRMATION
assert_epp_response :completed_successfully_action_pending
end
@ -90,7 +89,9 @@ class EppDomainDeleteBaseTest < EppTestCase
</epp>
XML
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
perform_enqueued_jobs do
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
@domain.reload
assert @domain.registrant_verification_asked?
@ -121,7 +122,9 @@ class EppDomainDeleteBaseTest < EppTestCase
</epp>
XML
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
perform_enqueued_jobs do
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
@domain.reload
assert_not @domain.registrant_verification_asked?
@ -152,7 +155,9 @@ class EppDomainDeleteBaseTest < EppTestCase
</epp>
XML
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
perform_enqueued_jobs do
post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
@domain.reload
assert_not @domain.registrant_verification_asked?

View file

@ -0,0 +1,22 @@
require 'test_helper'
class ReppV1BalanceTest < ActionDispatch::IntegrationTest
def setup
@registrar = users(:api_bestnames)
token = Base64.encode64("#{@registrar.username}:#{@registrar.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_can_query_balance
get '/repp/v1/accounts/balance', headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
assert_equal @registrar.registrar.cash_account.balance.to_s, json[:data][:balance]
assert_equal @registrar.registrar.cash_account.currency, json[:data][:currency]
end
end

View file

@ -0,0 +1,63 @@
require 'test_helper'
class ReppV1BaseTest < ActionDispatch::IntegrationTest
def setup
@registrar = users(:api_bestnames)
token = Base64.encode64("#{@registrar.username}:#{@registrar.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_unauthorized_user_has_no_access
get repp_v1_contacts_path
response_json = JSON.parse(response.body, symbolize_names: true)
assert_response :unauthorized
assert_equal 'Invalid authorization information', response_json[:message]
invalid_token = Base64.encode64("nonexistant:user")
headers = { 'Authorization' => "Basic #{invalid_token}" }
get repp_v1_contacts_path, headers: headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_response :unauthorized
assert_equal 'Invalid authorization information', response_json[:message]
end
def test_authenticates_valid_user
get repp_v1_contacts_path, headers: @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
end
def test_processes_invalid_base64_token_format_properly
token = '??as8d9sf kjsdjh klsdfjjf'
headers = { 'Authorization' => "Basic #{token}"}
get repp_v1_contacts_path, headers: headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_response :unauthorized
assert_equal 'Invalid authorization information', response_json[:message]
end
def test_takes_ip_whitelist_into_account
Setting.api_ip_whitelist_enabled = true
Setting.registrar_ip_whitelist_enabled = true
whiteip = white_ips(:one)
whiteip.update(ipv4: '1.1.1.1')
get repp_v1_contacts_path, headers: @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_response :unauthorized
assert_equal 2202, response_json[:code]
assert response_json[:message].include? 'Access denied from IP'
Setting.api_ip_whitelist_enabled = false
Setting.registrar_ip_whitelist_enabled = false
end
end

View file

@ -0,0 +1,30 @@
require 'test_helper'
class ReppV1ContactsCheckTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_code_based_check_returns_true_for_available_contact
get '/repp/v1/contacts/check/nonexistant:code', headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 'nonexistant:code', json[:data][:contact][:id]
assert_equal true, json[:data][:contact][:available]
end
def test_code_based_check_returns_true_for_available_contact
contact = contacts(:jack)
get "/repp/v1/contacts/check/#{contact.code}", headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal contact.code, json[:data][:contact][:id]
assert_equal false, json[:data][:contact][:available]
end
end

View file

@ -0,0 +1,156 @@
require 'test_helper'
class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_creates_new_contact
request_body = {
"contact": {
"name": "Donald Trump",
"phone": "+372.51111112",
"email": "donald@trumptower.com",
"ident": {
"ident_type": "priv",
"ident_country_code": "EE",
"ident": "39708290069"
}
}
}
post '/repp/v1/contacts', headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
contact = Contact.find_by(code: json[:data][:contact][:id])
assert contact.present?
assert_equal(request_body[:contact][:name], contact.name)
assert_equal(request_body[:contact][:phone], contact.phone)
assert_equal(request_body[:contact][:email], contact.email)
assert_equal(request_body[:contact][:ident][:ident_type], contact.ident_type)
assert_equal(request_body[:contact][:ident][:ident_country_code], contact.ident_country_code)
assert_equal(request_body[:contact][:ident][:ident], contact.ident)
end
def test_removes_postal_info_when_contact_created
request_body = {
"contact": {
"name": "Donald Trump",
"phone": "+372.51111111",
"email": "donald@trump.com",
"ident": {
"ident_type": "priv",
"ident_country_code": "EE",
"ident": "39708290069"
},
"addr": {
"city": "Tallinn",
"street": "Wismari 13",
"zip": "12345",
"country_code": "EE"
}
}
}
post '/repp/v1/contacts', headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1100, json[:code]
assert_equal 'Command completed successfully; Postal address data discarded', json[:message]
contact = Contact.find_by(code: json[:data][:contact][:id])
assert contact.present?
assert_nil contact.city
assert_nil contact.street
assert_nil contact.zip
assert_nil contact.country_code
end
def test_requires_contact_address_when_processing_enabled
Setting.address_processing = true
request_body = {
"contact": {
"name": "Donald Trump",
"phone": "+372.51111112",
"email": "donald@trumptower.com",
"ident": {
"ident_type": "priv",
"ident_country_code": "EE",
"ident": "39708290069"
}
}
}
post '/repp/v1/contacts', headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2003, json[:code]
assert json[:message].include? 'param is missing or the value is empty'
Setting.address_processing = false
end
def test_validates_ident_code
request_body = {
"contact": {
"name": "Donald Trump",
"phone": "+372.51111112",
"email": "donald@trumptower.com",
"ident": {
"ident_type": "priv",
"ident_country_code": "EE",
"ident": "123123123"
}
}
}
post '/repp/v1/contacts', headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2005, json[:code]
assert json[:message].include? 'Ident code does not conform to national identification number format'
end
def test_attaches_legaldoc_if_present
request_body = {
"contact": {
"name": "Donald Trump",
"phone": "+372.51111112",
"email": "donald@trumptower.com",
"ident": {
"ident_type": "priv",
"ident_country_code": "EE",
"ident": "39708290069"
},
},
"legal_document": {
"type": "pdf",
"body": "#{'test' * 2000}"
}
}
post '/repp/v1/contacts', headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
contact = Contact.find_by(code: json[:data][:contact][:id])
assert contact.legal_documents.any?
end
end

View file

@ -0,0 +1,47 @@
require 'test_helper'
class ReppV1ContactsDeleteTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_deletes_unassociated_contact
contact = contacts(:invalid_email)
delete "/repp/v1/contacts/#{contact.code}", headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
end
def test_can_not_delete_associated_contact
contact = contacts(:john)
delete "/repp/v1/contacts/#{contact.code}", headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2305, json[:code]
assert_equal 'Object association prohibits operation [domains]', json[:message]
end
def test_handles_unknown_contact
delete "/repp/v1/contacts/definitely:unexistant", headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :not_found
end
def test_can_not_destroy_other_registrar_contact
contact = contacts(:jack)
delete "/repp/v1/contacts/#{contact.code}", headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :not_found
end
end

View file

@ -0,0 +1,55 @@
require 'test_helper'
class ReppV1ContactsListTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_returns_registrar_contacts
get repp_v1_contacts_path, headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal @user.registrar.contacts.count, json[:total_number_of_records]
assert_equal @user.registrar.contacts.count, json[:contacts].length
assert json[:contacts][0].is_a? String
end
def test_returns_detailed_registrar_contacts
get repp_v1_contacts_path(details: true), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal @user.registrar.contacts.count, json[:total_number_of_records]
assert_equal @user.registrar.contacts.count, json[:contacts].length
assert json[:contacts][0].is_a? Hash
end
def test_respects_limit
get repp_v1_contacts_path(details: true, limit: 2), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 2, json[:contacts].length
end
def test_respects_offset
offset = 1
get repp_v1_contacts_path(details: true, offset: offset), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal (@user.registrar.contacts.count - offset), json[:contacts].length
end
end

View file

@ -0,0 +1,45 @@
require 'test_helper'
class ReppV1ContactsShowTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_returns_error_when_not_found
get repp_v1_contact_path(id: 'definitelynotexistant'), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :not_found
assert_equal 2303, json[:code]
assert_equal 'Object does not exist', json[:message]
end
def test_shows_existing_contact
contact = @user.registrar.contacts.first
get repp_v1_contact_path(id: contact.code), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
assert_equal contact.code, json[:data][:id]
end
def test_can_not_access_out_of_scope_contacts
# Contact of registrar goodnames, we're using bestnames API credentials
contact = contacts(:jack)
get repp_v1_contact_path(id: contact.code), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :not_found
assert_equal 2303, json[:code]
assert_equal 'Object does not exist', json[:message]
end
end

View file

@ -0,0 +1,119 @@
require 'test_helper'
class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest
def setup
@contact = contacts(:john)
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_updates_contact
request_body = {
"contact": {
"email": "donaldtrump@yandex.ru"
}
}
put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
contact = Contact.find_by(code: json[:data][:contact][:id])
assert contact.present?
assert_equal(request_body[:contact][:email], contact.email)
end
def test_removes_postal_info_when_updated
request_body = {
"contact": {
"addr": {
"city": "Tallinn",
"street": "Wismari 13",
"zip": "12345",
"country_code": "EE"
}
}
}
put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1100, json[:code]
assert_equal 'Command completed successfully; Postal address data discarded', json[:message]
contact = Contact.find_by(code: json[:data][:contact][:id])
assert contact.present?
assert_nil contact.city
assert_nil contact.street
assert_nil contact.zip
assert_nil contact.country_code
end
def test_can_not_change_ident_code
request_body = {
"contact": {
"name": "Donald Trumpster",
"ident": {
"ident_type": "priv",
"ident_country_code": "US",
"ident": "12345"
}
}
}
put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
@contact.reload
assert_not @contact.ident == 12345
assert_response :bad_request
assert_equal 2308, json[:code]
assert json[:message].include? 'Ident update is not allowed. Consider creating new contact object'
end
def test_attaches_legaldoc_if_present
request_body = {
"contact": {
"email": "donaldtrump@yandex.ru"
},
"legal_document": {
"type": "pdf",
"body": "#{'test' * 2000}"
}
}
put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
@contact.reload
assert @contact.legal_documents.any?
end
def test_returns_error_if_ident_wrong_format
request_body = {
"contact": {
"ident": "123"
}
}
put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2308, json[:code]
assert_equal 'Ident update is not allowed. Consider creating new contact object', json[:message]
end
end

View file

@ -0,0 +1,65 @@
require 'test_helper'
class ReppV1DomainsContactReplacementTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_replaces_tech_contact_with_new_one
replaceable_contact = contacts(:william)
replacing_contact = contacts(:jane)
payload = {
"current_contact_id": replaceable_contact.code,
"new_contact_id": replacing_contact.code
}
patch '/repp/v1/domains/contacts', 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][:affected_domains].include? 'airport.test'
assert json[:data][:affected_domains].include? 'shop.test'
assert_empty json[:data][:skipped_domains]
end
def test_tech_contact_id_must_differ
replaceable_contact = contacts(:william)
replacing_contact = contacts(:william)
payload = {
"current_contact_id": replaceable_contact.code,
"new_contact_id": replacing_contact.code
}
patch '/repp/v1/domains/contacts', headers: @auth_headers, params: payload
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2304, json[:code]
assert_equal 'New contact must be different from current', json[:message]
end
def test_contact_codes_must_be_valid
payload = {
"current_contact_id": 'dfgsdfg',
"new_contact_id": 'vvv'
}
patch '/repp/v1/domains/contacts', headers: @auth_headers, params: payload
json = JSON.parse(response.body, symbolize_names: true)
assert_response :not_found
assert_equal 2303, json[:code]
assert_equal 'Object does not exist', json[:message]
end
end

View file

@ -0,0 +1,54 @@
require 'test_helper'
class ReppV1DomainsListTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_returns_registrar_domains
get repp_v1_domains_path, headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal @user.registrar.domains.count, json[:data][:total_number_of_records]
assert_equal @user.registrar.domains.count, json[:data][:domains].length
assert json[:data][:domains][0].is_a? String
end
def test_returns_detailed_registrar_domains
get repp_v1_domains_path(details: true), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal @user.registrar.domains.count, json[:data][:total_number_of_records]
assert_equal @user.registrar.domains.count, json[:data][:domains].length
assert json[:data][:domains][0].is_a? Hash
end
def test_respects_limit
get repp_v1_domains_path(details: true, limit: 2), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 2, json[:data][:domains].length
end
def test_respects_offset
offset = 1
get repp_v1_domains_path(details: true, offset: offset), headers: @auth_headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal (@user.registrar.domains.count - offset), json[:data][:domains].length
end
end

View file

@ -0,0 +1,40 @@
require 'test_helper'
class ReppV1DomainsTransferInfoTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@domain = domains(:shop)
@auth_headers = { 'Authorization' => token }
end
def test_can_query_domain_info
headers = @auth_headers
headers['Auth-Code'] = @domain.transfer_code
get "/repp/v1/domains/#{@domain.name}/transfer_info", headers: headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
assert_equal 'Command completed successfully', json[:message]
assert_equal @domain.name, json[:data][:domain]
assert json[:data][:registrant].present?
assert json[:data][:admin_contacts].present?
assert json[:data][:tech_contacts].present?
end
def test_respects_domain_authorization_code
headers = @auth_headers
headers['Auth-Code'] = 'jhfgifhdg'
get "/repp/v1/domains/#{@domain.name}/transfer_info", headers: headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2202, json[:code]
assert_equal 'Authorization error', json[:message]
assert_empty json[:data]
end
end

View file

@ -0,0 +1,127 @@
require 'test_helper'
class ReppV1DomainsTransferTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@domain = domains(:hospital)
@auth_headers = { 'Authorization' => token }
end
def test_transfers_domain
payload = {
"data": {
"domain_transfers": [
{ "domain_name": @domain.name, "transfer_code": @domain.transfer_code }
]
}
}
post "/repp/v1/domains/transfer", 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_equal @domain.name, json[:data][:success][0][:domain_name]
@domain.reload
assert @domain.registrar = @user.registrar
end
def test_does_not_transfer_domain_if_not_transferable
@domain.schedule_force_delete(type: :fast_track)
payload = {
"data": {
"domain_transfers": [
{ "domain_name": @domain.name, "transfer_code": @domain.transfer_code }
]
}
}
post "/repp/v1/domains/transfer", 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_equal 'Object status prohibits operation', json[:data][:failed][0][:errors][0][:msg]
@domain.reload
assert_not @domain.registrar == @user.registrar
end
def test_does_not_transfer_domain_with_invalid_auth_code
payload = {
"data": {
"domain_transfers": [
{ "domain_name": @domain.name, "transfer_code": "sdfgsdfg" }
]
}
}
post "/repp/v1/domains/transfer", 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_equal "Invalid authorization information", json[:data][:failed][0][:errors][0][:msg]
end
def test_does_not_transfer_domain_to_same_registrar
@domain.update!(registrar: @user.registrar)
payload = {
"data": {
"domain_transfers": [
{ "domain_name": @domain.name, "transfer_code": @domain.transfer_code }
]
}
}
post "/repp/v1/domains/transfer", 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_equal 'Domain already belongs to the querying registrar', json[:data][:failed][0][:errors][0][:msg]
@domain.reload
assert @domain.registrar == @user.registrar
end
def test_does_not_transfer_domain_if_discarded
@domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE])
payload = {
"data": {
"domain_transfers": [
{ "domain_name": @domain.name, "transfer_code": @domain.transfer_code }
]
}
}
post "/repp/v1/domains/transfer", 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_equal 'Object is not eligible for transfer', json[:data][:failed][0][:errors][0][:msg]
@domain.reload
assert_not @domain.registrar == @user.registrar
end
end

View file

@ -0,0 +1,77 @@
require 'test_helper'
class ReppV1RegistrarNameserversTest < ActionDispatch::IntegrationTest
def setup
@user = users(:api_bestnames)
token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
token = "Basic #{token}"
@auth_headers = { 'Authorization' => token }
end
def test_updates_nameserver_values
nameserver = nameservers(:shop_ns1)
payload = {
"data": {
"id": nameserver.hostname,
"type": "nameserver",
"attributes": {
"hostname": "#{nameserver.hostname}.test",
"ipv4": ["1.1.1.1"]
}
}
}
put '/repp/v1/registrar/nameservers', 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_equal({ hostname: "#{nameserver.hostname}.test", ipv4: ["1.1.1.1"] }, json[:data][:attributes])
assert_equal({ hostname: "#{nameserver.hostname}.test", ipv4: ["1.1.1.1"] }, json[:data][:attributes])
assert json[:data][:affected_domains].include? 'airport.test'
assert json[:data][:affected_domains].include? 'shop.test'
end
def test_nameserver_with_hostname_must_exist
payload = {
"data": {
"id": 'ns.nonexistant.test',
"type": "nameserver",
"attributes": {
"hostname": "ns1.dn.test",
"ipv4": ["1.1.1.1"]
}
}
}
put '/repp/v1/registrar/nameservers', headers: @auth_headers, params: payload
json = JSON.parse(response.body, symbolize_names: true)
assert_response :not_found
assert_equal 2303, json[:code]
assert_equal 'Object does not exist', json[:message]
end
def test_ip_must_be_in_correct_format
nameserver = nameservers(:shop_ns1)
payload = {
"data": {
"id": nameserver.hostname,
"type": "nameserver",
"attributes": {
"hostname": "#{nameserver.hostname}.test",
"ipv6": ["1.1.1.1"]
}
}
}
put '/repp/v1/registrar/nameservers', headers: @auth_headers, params: payload
json = JSON.parse(response.body, symbolize_names: true)
assert_response :bad_request
assert_equal 2005, json[:code]
assert_equal 'IPv6 is invalid [ipv6]', json[:message]
end
end

View file

@ -1,18 +1,20 @@
require 'test_helper'
class NewDomainForceDeleteTest < ActiveSupport::TestCase
class ForceDeleteTest < ActionMailer::TestCase
setup do
@domain = domains(:shop)
Setting.redemption_grace_period = 30
ActionMailer::Base.deliveries.clear
end
def test_schedules_force_delete_fast_track
assert_not @domain.force_delete_scheduled?
travel_to Time.zone.parse('2010-07-05')
@domain.schedule_force_delete(type: :fast_track)
@domain.schedule_force_delete(type: :fast_track, notify_by_email: true)
@domain.reload
assert_emails 1
assert @domain.force_delete_scheduled?
assert_equal Date.parse('2010-08-20'), @domain.force_delete_date.to_date
assert_equal Date.parse('2010-07-06'), @domain.force_delete_start.to_date
@ -111,9 +113,12 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase
def test_force_delete_cannot_be_scheduled_when_a_domain_is_discarded
@domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE])
assert_raises StandardError do
@domain.schedule_force_delete(type: :fast_track)
end
result = ForceDeleteInteraction::SetForceDelete.run(domain: @domain, type: :fast_track)
assert_not result.valid?
assert_not @domain.force_delete_scheduled?
message = ["Force delete procedure cannot be scheduled while a domain is discarded"]
assert_equal message, result.errors.messages[:domain]
end
def test_cancels_force_delete

View file

@ -414,7 +414,7 @@ class DomainTest < ActiveSupport::TestCase
force_delete_date: nil)
@domain.update(template_name: 'legal_person')
travel_to Time.zone.parse('2010-07-05')
@domain.schedule_force_delete(type: :fast_track)
ForceDeleteInteraction::SetForceDelete.run!(domain: @domain, type: :fast_track)
assert(@domain.force_delete_scheduled?)
other_registrant = Registrant.find_by(code: 'jane-001')
@domain.pending_json['new_registrant_id'] = other_registrant.id

View file

@ -70,6 +70,6 @@ class Whois::RecordTest < ActiveSupport::TestCase
end
def registration_deadline
Time.zone.now + 10.days
@registration_deadline ||= Time.zone.now + 10.days
end
end

View file

@ -29,6 +29,16 @@ class RegistrarAreaBaseTestTest < ApplicationSystemTestCase
assert_button 'Login'
end
def test_user_can_access_when_ip_is_whitelisted_with_subnet
white_ips(:one).update!(ipv4: '127.0.0.1/32', interfaces: [WhiteIp::REGISTRAR])
Setting.registrar_ip_whitelist_enabled = true
visit new_registrar_user_session_url
assert_no_text 'Access denied from IP 127.0.0.1'
assert_button 'Login'
end
def test_user_can_access_when_ip_is_not_whitelisted_and_whitelist_is_disabled
Setting.registrar_ip_whitelist_enabled = false
WhiteIp.delete_all

View file

@ -6,9 +6,9 @@ class RegistrarAreaBulkTransferTest < ApplicationSystemTestCase
end
def test_transfer_multiple_domains_in_bulk
request_body = { data: { domainTransfers: [{ domainName: 'shop.test', transferCode: '65078d5' }] } }
request_body = { data: { domain_transfers: [{ domain_name: 'shop.test', transfer_code: '65078d5' }] } }
headers = { 'Content-type' => Mime[:json] }
request_stub = stub_request(:post, /domain_transfers/).with(body: request_body,
request_stub = stub_request(:post, /domains\/transfer/).with(body: request_body,
headers: headers,
basic_auth: ['test_goodnames', 'testtest'])
.to_return(body: { data: [{
@ -29,7 +29,7 @@ class RegistrarAreaBulkTransferTest < ApplicationSystemTestCase
def test_fail_gracefully
body = { errors: [{ title: 'epic fail' }] }.to_json
headers = { 'Content-type' => Mime[:json] }
stub_request(:post, /domain_transfers/).to_return(status: 400, body: body, headers: headers)
stub_request(:post, /domains\/transfer/).to_return(status: 400, body: body, headers: headers)
visit registrar_domains_url
click_link 'Bulk change'