Merge pull request #1774 from internetee/repp-domains

REPP: Domain management
This commit is contained in:
Timo Võhmar 2021-03-29 15:42:33 +03:00 committed by GitHub
commit d073656448
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 2700 additions and 605 deletions

View file

@ -1,3 +1,4 @@
require 'deserializers/xml/domain_delete'
module Epp
class DomainsController < BaseController
before_action :find_domain, only: %i[info renew update transfer delete]
@ -26,104 +27,39 @@ module Epp
end
def create
authorize! :create, Epp::Domain
authorize!(:create, Epp::Domain)
if Domain.release_to_auction
request_domain_name = params[:parsed_frame].css('name').text.strip.downcase
domain_name = DNS::DomainName.new(SimpleIDN.to_unicode(request_domain_name))
registrar_id = current_user.registrar.id
@domain = Epp::Domain.new
data = ::Deserializers::Xml::DomainCreate.new(params[:parsed_frame], registrar_id).call
action = Actions::DomainCreate.new(@domain, data)
if domain_name.at_auction?
epp_errors << {
code: '2306',
msg: 'Parameter value policy error: domain is at auction',
}
handle_errors
return
elsif domain_name.awaiting_payment?
epp_errors << {
code: '2003',
msg: 'Required parameter missing; reserved>pw element required for reserved domains',
}
handle_errors
return
elsif domain_name.pending_registration?
registration_code = params[:parsed_frame].css('reserved > pw').text
if registration_code.empty?
epp_errors << {
code: '2003',
msg: 'Required parameter missing; reserved>pw element is required',
}
handle_errors
return
end
unless domain_name.available_with_code?(registration_code)
epp_errors << {
code: '2202',
msg: 'Invalid authorization information; invalid reserved>pw value',
}
handle_errors
return
end
end
end
@domain = Epp::Domain.new_from_epp(params[:parsed_frame], current_user)
handle_errors(@domain) and return if @domain.errors.any?
@domain.valid?
@domain.errors.delete(:name_dirty) if @domain.errors[:puny_label].any?
handle_errors(@domain) and return if @domain.errors.any?
handle_errors and return unless balance_ok?('create') # loads pricelist in this method
ActiveRecord::Base.transaction do
@domain.add_legal_file_to_new(params[:parsed_frame])
if @domain.save # TODO: Maybe use validate: false here because we have already validated the domain?
current_user.registrar.debit!({
sum: @domain_pricelist.price.amount,
description: "#{I18n.t('create')} #{@domain.name}",
activity_type: AccountActivity::CREATE,
price: @domain_pricelist
})
if Domain.release_to_auction && domain_name.pending_registration?
active_auction = Auction.find_by(domain: domain_name.to_s,
status: Auction.statuses[:payment_received])
active_auction.domain_registered!
end
Dispute.close_by_domain(@domain.name)
render_epp_response '/epp/domains/create'
else
handle_errors(@domain)
end
end
action.call ? render_epp_response('/epp/domains/create') : handle_errors(@domain)
end
def update
authorize! :update, @domain, @password
authorize!(:update, @domain, @password)
updated = @domain.update(params[:parsed_frame], current_user)
(handle_errors(@domain) && return) unless updated
registrar_id = current_user.registrar.id
update_params = ::Deserializers::Xml::DomainUpdate.new(params[:parsed_frame],
registrar_id).call
action = Actions::DomainUpdate.new(@domain, update_params, false)
(handle_errors(@domain) and return) unless action.call
pending = @domain.epp_pending_update.present?
render_epp_response "/epp/domains/success#{'_pending' if pending}"
render_epp_response("/epp/domains/success#{'_pending' if pending}")
end
def delete
authorize! :delete, @domain, @password
authorize!(:delete, @domain, @password)
frame = params[:parsed_frame]
delete_params = ::Deserializers::Xml::DomainDelete.new(frame).call
action = Actions::DomainDelete.new(@domain, delete_params, current_user.registrar)
(handle_errors(@domain) && return) unless @domain.can_be_deleted?
(handle_errors(@domain) and return) unless action.call
if @domain.epp_destroy(params[:parsed_frame], current_user.id)
if @domain.epp_pending_delete.present?
render_epp_response '/epp/domains/success_pending'
else
render_epp_response '/epp/domains/success'
end
else
handle_errors(@domain)
end
pending = @domain.epp_pending_delete.present?
render_epp_response("/epp/domains/success#{'_pending' if pending}")
end
def check
@ -137,42 +73,15 @@ module Epp
def renew
authorize! :renew, @domain
period_element = params[:parsed_frame].css('period').text
period = (period_element.to_i == 0) ? 1 : period_element.to_i
period_unit = Epp::Domain.parse_period_unit_from_frame(params[:parsed_frame]) || 'y'
registrar_id = current_user.registrar.id
renew_params = ::Deserializers::Xml::Domain.new(params[:parsed_frame],
registrar_id).call
balance_ok?('renew', period, period_unit) # loading pricelist
begin
ActiveRecord::Base.transaction(isolation: :serializable) do
@domain.reload
success = @domain.renew(
params[:parsed_frame].css('curExpDate').text,
period, period_unit
)
if success
unless balance_ok?('renew', period, period_unit)
handle_errors
fail ActiveRecord::Rollback
end
current_user.registrar.debit!({
sum: @domain_pricelist.price.amount,
description: "#{I18n.t('renew')} #{@domain.name}",
activity_type: AccountActivity::RENEW,
price: @domain_pricelist
})
render_epp_response '/epp/domains/renew'
else
handle_errors(@domain)
end
end
rescue ActiveRecord::StatementInvalid => e
sleep rand / 100
retry
action = Actions::DomainRenew.new(@domain, renew_params, current_user.registrar)
if action.call
render_epp_response '/epp/domains/renew'
else
handle_errors(@domain)
end
end

View file

@ -1,6 +1,8 @@
module Repp
module V1
class AccountsController < BaseController
api :GET, '/repp/v1/accounts/balance'
desc "Get account's balance"
def balance
resp = { balance: current_user.registrar.cash_account.balance,
currency: current_user.registrar.cash_account.currency }

View file

@ -1,6 +1,6 @@
module Repp
module V1
class BaseController < ActionController::API
class BaseController < ActionController::API # rubocop:disable Metrics/ClassLength
around_action :log_request
before_action :authenticate_user
before_action :validate_webclient_ca
@ -16,9 +16,12 @@ module Repp
rescue ActiveRecord::RecordNotFound
@response = { code: 2303, message: 'Object does not exist' }
render(json: @response, status: :not_found)
rescue ActionController::ParameterMissing => e
rescue ActionController::ParameterMissing, Apipie::ParamMissing => e
@response = { code: 2003, message: e }
render(json: @response, status: :bad_request)
rescue Apipie::ParamInvalid => e
@response = { code: 2005, message: e }
render(json: @response, status: :bad_request)
ensure
create_repp_log
end
@ -35,6 +38,16 @@ module Repp
end
# rubocop:enable Metrics/AbcSize
def set_domain
registrar = current_user.registrar
@domain = Epp::Domain.find_by(registrar: registrar, name: params[:domain_id])
@domain ||= Epp::Domain.find_by!(registrar: registrar, name_puny: params[:domain_id])
return @domain if @domain
raise ActiveRecord::RecordNotFound
end
def set_paper_trail_whodunnit
::PaperTrail.request.whodunnit = current_user
end

View file

@ -1,10 +1,11 @@
require 'serializers/repp/contact'
module Repp
module V1
class ContactsController < BaseController
class ContactsController < BaseController # rubocop:disable Metrics/ClassLength
before_action :find_contact, only: %i[show update destroy]
## GET /repp/v1/contacts
api :get, '/repp/v1/contacts'
desc 'Get all existing contacts'
def index
record_count = current_user.registrar.contacts.count
contacts = showable_contacts(params[:details], params[:limit] || 200,
@ -13,14 +14,16 @@ module Repp
render(json: @response, status: :ok)
end
## GET /repp/v1/contacts/1
api :get, '/repp/v1/contacts/:contact_code'
desc 'Get a specific contact'
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
api :get, '/repp/v1/contacts/check/:contact_code'
desc 'Check contact code availability'
def check
contact = Epp::Contact.find_by(code: params[:id])
data = { contact: { id: params[:id], available: contact.nil? } }
@ -28,7 +31,8 @@ module Repp
render_success(data: data)
end
## POST /repp/v1/contacts
api :POST, '/repp/v1/contacts'
desc 'Create a new contact'
def create
@contact = Epp::Contact.new(contact_params_with_address, current_user.registrar, epp: false)
action = Actions::ContactCreate.new(@contact, params[:legal_document],
@ -42,7 +46,8 @@ module Repp
render_success(create_update_success_body)
end
## PUT /repp/v1/contacts/1
api :PUT, '/repp/v1/contacts/:contact_code'
desc 'Update existing contact'
def update
action = Actions::ContactUpdate.new(@contact, contact_params_with_address(required: false),
params[:legal_document],
@ -56,6 +61,8 @@ module Repp
render_success(create_update_success_body)
end
api :DELETE, '/repp/v1/contacts/:contact_code'
desc 'Delete a specific contact'
def destroy
action = Actions::ContactDelete.new(@contact, params[:legal_document])
unless action.call

View file

@ -2,6 +2,50 @@ module Repp
module V1
module Domains
class ContactsController < BaseContactsController
before_action :set_domain, only: %i[index create destroy]
def_param_group :contacts_apidoc do
param :contacts, Array, required: true, desc: 'Array of new linked contacts' do
param :code, String, required: true, desc: 'Contact code'
param :type, String, required: true, desc: 'Role of contact (admin/tech)'
end
end
api :GET, '/repp/v1/domains/:domain_name/contacts'
desc "View domain's admin and tech contacts"
def index
admin_contacts = @domain.admin_domain_contacts.pluck(:contact_code_cache)
tech_contacts = @domain.tech_domain_contacts.pluck(:contact_code_cache)
data = { admin_contacts: admin_contacts, tech_contacts: tech_contacts }
render_success(data: data)
end
api :POST, '/repp/v1/domains/:domain_name/contacts'
desc 'Link new contact(s) to domain'
param_group :contacts_apidoc
def create
cta('add')
end
api :DELETE, '/repp/v1/domains/:domain_name/contacts'
desc 'Remove contact(s) from domain'
param_group :contacts_apidoc
def destroy
cta('rem')
end
def cta(action = 'add')
params[:contacts].each { |c| c[:action] = action }
action = Actions::DomainUpdate.new(@domain, contact_create_params, false)
# rubocop:disable Style/AndOr
handle_errors(@domain) and return unless action.call
# rubocop:enable Style/AndOr
render_success(data: { domain: { name: @domain.name } })
end
def update
super
@ -15,6 +59,12 @@ module Repp
@response = { affected_domains: affected, skipped_domains: skipped }
render_success(data: @response)
end
private
def contact_create_params
params.permit(:domain_id, contacts: [%i[action code type]])
end
end
end
end

View file

@ -0,0 +1,58 @@
module Repp
module V1
module Domains
class DnssecController < BaseController
before_action :set_domain, only: %i[index create destroy]
def_param_group :dns_keys_apidoc do
param :flags, String, required: true, desc: '256 (KSK) or 257 (ZSK)'
param :protocol, String, required: true, desc: 'Key protocol (3)'
param :alg, String, required: true, desc: 'DNSSEC key algorithm (3,5,6,7,8,10,13,14)'
param :public_key, String, required: true, desc: 'DNSSEC public key'
end
api :GET, '/repp/v1/domains/:domain_name/dnssec'
desc "View specific domain's DNSSEC keys"
def index
dnssec_keys = @domain.dnskeys
data = { dns_keys: dnssec_keys.as_json(only: %i[flags alg protocol public_key]) }
render_success(data: data)
end
api :POST, '/repp/v1/domains/:domain_name/dnssec'
desc 'Create a new DNSSEC key(s) for domain'
param :dns_keys, Array, required: true, desc: 'Array of new DNSSEC keys' do
param_group :dns_keys_apidoc, DnssecController
end
def create
cta('add')
end
api :DELETE, 'repp/v1/domains/:domain_name/dnssec'
param :dns_keys, Array, required: true, desc: 'Array of new DNSSEC keys' do
param_group :dns_keys_apidoc, DnssecController
end
def destroy
cta('rem')
end
def cta(action = 'add')
params[:dns_keys].each { |n| n[:action] = action }
action = Actions::DomainUpdate.new(@domain, dnssec_params, false)
# rubocop:disable Style/AndOr
(handle_errors(@domain) and return) unless action.call
# rubocop:enable Style/AndOr
render_success(data: { domain: { name: @domain.name } })
end
private
def dnssec_params
params.permit(:domain_id, dns_keys: [%i[action flags protocol alg public_key]])
end
end
end
end
end

View file

@ -0,0 +1,61 @@
module Repp
module V1
module Domains
class NameserversController < BaseController
before_action :set_domain, only: %i[index create destroy]
before_action :set_nameserver, only: %i[destroy]
api :GET, '/repp/v1/domains/:domain_name/nameservers'
desc "Get domain's nameservers"
def index
nameservers = @domain.nameservers
data = { nameservers: nameservers.as_json(only: %i[hostname ipv4 ipv6]) }
render_success(data: data)
end
api :POST, '/repp/v1/domains/:domain_name/nameservers'
desc 'Create new nameserver for domain'
param :nameservers, Array, required: true, desc: 'Array of new nameservers' do
param :hostname, String, required: true, desc: 'Nameserver hostname'
param :ipv4, Array, required: false, desc: 'Array of IPv4 values'
param :ipv6, Array, required: false, desc: 'Array of IPv6 values'
end
def create
params[:nameservers].each { |n| n[:action] = 'add' }
action = Actions::DomainUpdate.new(@domain, nameserver_params, current_user)
unless action.call
handle_errors(@domain)
return
end
render_success(data: { domain: { name: @domain.name } })
end
api :DELETE, '/repp/v1/domains/:domain/nameservers/:nameserver'
desc 'Delete specific nameserver from domain'
def destroy
nameserver = { nameservers: [{ hostname: params[:id], action: 'rem' }] }
action = Actions::DomainUpdate.new(@domain, nameserver, false)
unless action.call
handle_errors(@domain)
return
end
render_success(data: { domain: { name: @domain.name } })
end
private
def set_nameserver
@nameserver = @domain.nameservers.find_by!(hostname: params[:id])
end
def nameserver_params
params.permit(:domain_id, nameservers: [[:hostname, :action, ipv4: [], ipv6: []]])
end
end
end
end
end

View file

@ -4,6 +4,26 @@ module Repp
class RenewsController < BaseController
before_action :validate_renew_period, only: [:bulk_renew]
before_action :select_renewable_domains, only: [:bulk_renew]
before_action :set_domain, only: [:create]
api :POST, 'repp/v1/domains/:domain_name/renew'
desc 'Renew domain'
param :renew, Hash, required: true, desc: 'Renew parameters' do
param :period, Integer, required: true, desc: 'Renew period. Month (m) or year (y)'
param :period_unit, String, required: true, desc: 'For how many months or years to renew'
param :exp_date, String, required: true, desc: 'Current expiry date for domain'
end
def create
authorize!(:renew, @domain)
action = Actions::DomainRenew.new(@domain, renew_params[:renew], current_user.registrar)
unless action.call
handle_errors(@domain)
return
end
render_success(data: { domain: { name: @domain.name } })
end
def bulk_renew
renew = run_bulk_renew_task(@domains, bulk_renew_params[:renew_period])
@ -16,6 +36,10 @@ module Repp
private
def renew_params
params.permit(:domain_id, renew: %i[period period_unit exp_date])
end
def validate_renew_period
@epp_errors ||= []
periods = Depp::Domain::PERIODS.map { |p| p[1] }

View file

@ -0,0 +1,65 @@
module Repp
module V1
module Domains
class StatusesController < BaseController
before_action :set_domain, only: %i[update destroy]
before_action :verify_status
api :DELETE, '/repp/v1/domains/:domain_name/statuses/:status'
param :domain_name, String, desc: 'Domain name'
param :status, String, desc: 'Status to be removed'
desc 'Remove status from specific domain'
def destroy
return editing_failed unless domain_with_status?(params[:id])
@domain.statuses = @domain.statuses.delete(params[:id])
if @domain.save
render_success
else
handle_errors(@domain)
end
end
api :PUT, '/repp/v1/domains/:domain_name/statuses/:status'
param :domain_name, String, desc: 'Domain name'
param :status, String, desc: 'Status to be added'
desc 'Add status to specific domain'
def update
return editing_failed if domain_with_status?(params[:id])
@domain.statuses << params[:id]
if @domain.save
render_success(data: { domain: @domain.name, status: params[:id] })
else
handle_errors(@domain)
end
end
private
def domain_with_status?(status)
@domain.statuses.include?(status)
end
def verify_status
allowed_statuses = [DomainStatus::CLIENT_HOLD].freeze
stat = params[:id]
return if allowed_statuses.include?(stat)
@domain.add_epp_error('2306', nil, nil,
"#{I18n.t(:client_side_status_editing_error)}: status #{stat}")
handle_errors(@domain)
end
def editing_failed
stat = params[:id]
@domain.add_epp_error('2306', nil, nil,
"#{I18n.t(:client_side_status_editing_error)}: status #{stat}")
handle_errors(@domain)
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module Repp
module V1
module Domains
class TransfersController < BaseController
before_action :set_domain, only: [:create]
api :POST, 'repp/v1/domains/:domain_name/transfer'
desc 'Transfer a specific domain'
param :transfer, Hash, required: true, desc: 'Renew parameters' do
param :transfer_code, String, required: true, desc: 'Renew period. Month (m) or year (y)'
end
def create
action = Actions::DomainTransfer.new(@domain, transfer_params[:transfer][:transfer_code],
current_user.registrar)
unless action.call
handle_errors(@domain)
return
end
render_success(data: { domain: { name: @domain.name, type: 'domain_transfer' } })
end
private
def set_domain
domain_id = transfer_params[:domain_id]
h = {}
h[domain_id.match?(/\A[0-9]+\z/) ? :id : :name] = domain_id
@domain = Epp::Domain.find_by!(h)
end
def transfer_params
params.permit(:domain_id, transfer: [:transfer_code])
end
end
end
end
end

View file

@ -1,16 +1,96 @@
require 'serializers/repp/domain'
module Repp
module V1
class DomainsController < BaseController
before_action :set_authorized_domain, only: [:transfer_info]
class DomainsController < BaseController # rubocop:disable Metrics/ClassLength
before_action :set_authorized_domain, only: %i[transfer_info destroy]
before_action :validate_registrar_authorization, only: %i[transfer_info destroy]
before_action :forward_registrar_id, only: %i[create update destroy]
before_action :set_domain, only: %i[update]
api :GET, '/repp/v1/domains'
desc 'Get all existing domains'
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 })
render_success(data: { domains: serialized_domains(domains),
total_number_of_records: records.count })
end
api :GET, '/repp/v1/domains/:domain_name'
desc 'Get a specific domain'
def show
@domain = Epp::Domain.find_by!(name: params[:id])
sponsor = @domain.registrar == current_user.registrar
render_success(data: { domain: Serializers::Repp::Domain.new(@domain,
sponsored: sponsor).to_json })
end
api :POST, '/repp/v1/domains'
desc 'Create a new domain'
param :domain, Hash, required: true, desc: 'Parameters for new domain' do
param :name, String, required: true, desc: 'Domain name to be registered'
param :registrant, String, required: true, desc: 'Registrant contact code'
param :reserved_pw, String, required: false, desc: 'Reserved password for domain'
param :transfer_code, String, required: false, desc: 'Desired transfer code for domain'
param :period, Integer, required: true, desc: 'Registration period in months or years'
param :period_unit, String, required: true, desc: 'Period type (month m) or (year y)'
param :nameservers_attributes, Array, required: false, desc: 'Domain nameservers' do
param :hostname, String, required: true, desc: 'Nameserver hostname'
param :ipv4, Array, desc: 'Array of IPv4 addresses'
param :ipv6, Array, desc: 'Array of IPv4 addresses'
end
param :admin_contacts, Array, required: false, desc: 'Admin domain contacts codes'
param :tech_contacts, Array, required: false, desc: 'Tech domain contacts codes'
param :dnskeys_attributes, Array, required: false, desc: 'DNSSEC keys for domain' do
param_group :dns_keys_apidoc, Repp::V1::Domains::DnssecController
end
end
returns code: 200, desc: 'Successful domain registration response' do
property :code, Integer, desc: 'EPP code'
property :message, String, desc: 'EPP code explanation'
property :data, Hash do
property :domain, Hash do
property :name, String, 'Domain name'
end
end
end
def create
authorize!(:create, Epp::Domain)
@domain = Epp::Domain.new
action = Actions::DomainCreate.new(@domain, domain_create_params)
# rubocop:disable Style/AndOr
handle_errors(@domain) and return unless action.call
# rubocop:enable Style/AndOr
render_success(data: { domain: { name: @domain.name } })
end
api :PUT, '/repp/v1/domains/:domain_name'
desc 'Update existing domain'
param :id, String, desc: 'Domain name in IDN / Puny format'
param :domain, Hash, required: true, desc: 'Changes of domain object' do
param :registrant, Hash, required: false, desc: 'New registrant object' do
param :code, String, required: true, desc: 'New registrant contact code'
param :verified, [true, false], required: false,
desc: 'Registrant change is already verified'
end
param :transfer_code, String, required: false, desc: 'New authorization code'
end
def update
action = Actions::DomainUpdate.new(@domain, params[:domain], false)
unless action.call
handle_errors(@domain)
return
end
render_success(data: { domain: { name: @domain.name } })
end
api :GET, '/repp/v1/domains/:domain_name/transfer_info'
desc "Retrieve specific domain's transfer info"
def transfer_info
contact_fields = %i[code name ident ident_type ident_country_code phone email street city
zip country_code statuses]
@ -25,6 +105,8 @@ module Repp
render_success(data: data)
end
api :POST, '/repp/v1/domains/transfer'
desc 'Transfer multiple domains'
def transfer
@errors ||= []
@successful = []
@ -36,6 +118,30 @@ module Repp
render_success(data: { success: @successful, failed: @errors })
end
api :DELETE, '/repp/v1/domains/:domain_name'
desc 'Delete specific domain'
param :delete, Hash, required: true, desc: 'Object holding verified key' do
param :verified, [true, false], required: true,
desc: 'Whether to ask registrant verification or not'
end
def destroy
action = Actions::DomainDelete.new(@domain, params, current_user.registrar)
# rubocop:disable Style/AndOr
handle_errors(@domain) and return unless action.call
# rubocop:enable Style/AndOr
render_success(data: { domain: { name: @domain.name } })
end
private
def serialized_domains(domains)
return domains.pluck(:name) unless index_params[:details] == 'true'
domains.map { |d| Serializers::Repp::Domain.new(d).to_json }
end
def initiate_transfer(transfer)
domain = Epp::Domain.find_or_initialize_by(name: transfer[:domain_name])
action = Actions::DomainTransfer.new(domain, transfer[:transfer_code],
@ -49,8 +155,6 @@ module Repp
end
end
private
def transfer_params
params.require(:data).require(:domain_transfers).each do |t|
t.require(:domain_name)
@ -66,10 +170,29 @@ module Repp
params.permit(:id)
end
def forward_registrar_id
return unless params[:domain]
params[:domain][:registrar] = current_user.registrar.id
end
def set_domain
registrar = current_user.registrar
@domain = Epp::Domain.find_by(registrar: registrar, name: params[:id])
@domain ||= Epp::Domain.find_by!(registrar: registrar, name_puny: params[:id])
return @domain if @domain
raise ActiveRecord::RecordNotFound
end
def set_authorized_domain
@epp_errors ||= []
@domain = domain_from_url_hash
end
def validate_registrar_authorization
return if @domain.registrar == current_user.registrar
return if @domain.transfer_code.eql?(request.headers['Auth-Code'])
@epp_errors << { code: 2202, msg: I18n.t('errors.messages.epp_authorization_error') }
@ -78,9 +201,9 @@ module Repp
def domain_from_url_hash
entry = transfer_info_params[:id]
return Domain.find(entry) if entry.match?(/\A[0-9]+\z/)
return Epp::Domain.find(entry) if entry.match?(/\A[0-9]+\z/)
Domain.find_by!('name = ? OR name_puny = ?', entry, entry)
Epp::Domain.find_by!('name = ? OR name_puny = ?', entry, entry)
end
def limit
@ -94,6 +217,14 @@ module Repp
def index_params
params.permit(:limit, :offset, :details)
end
def domain_create_params
params.require(:domain).permit(:name, :registrant, :period, :period_unit, :registrar,
:transfer_code, :reserved_pw,
dnskeys_attributes: [%i[flags alg protocol public_key]],
nameservers_attributes: [[:hostname, ipv4: [], ipv6: []]],
admin_contacts: [], tech_contacts: [])
end
end
end
end

View file

@ -4,6 +4,19 @@ module Repp
class NameserversController < BaseController
before_action :verify_nameserver_existance, only: %i[update]
api :PUT, 'repp/v1/registrar/nameservers'
desc 'bulk nameserver change'
param :data, Hash, required: true, desc: 'Object holding nameserver changes' do
param :type, String, required: true, desc: 'Always set as "nameserver"'
param :id, String, required: true, desc: 'Hostname of replacable nameserver'
param :domains, Array, required: false, desc: 'Array of domain names qualified for ' \
'nameserver replacement'
param :attributes, Hash, required: true, desc: 'Object holding new nameserver values' do
param :hostname, String, required: true, desc: 'New hostname of nameserver'
param :ipv4, Array, of: String, required: false, desc: 'Array of fixed IPv4 addresses'
param :ipv6, Array, of: String, required: false, desc: 'Array of fixed IPv6 addresses'
end
end
def update
affected, errored = current_user.registrar
.replace_nameservers(hostname,

View file

@ -0,0 +1,51 @@
module Repp
module V1
module Registrar
class NotificationsController < BaseController
before_action :set_notification, only: [:update]
api :GET, '/repp/v1/registrar/notifications'
desc 'Get the latest unread poll message'
def index
@notification = current_user.unread_notifications.order('created_at DESC').take
# rubocop:disable Style/AndOr
render_success(data: nil) and return unless @notification
# rubocop:enable Style/AndOr
data = @notification.as_json(only: %i[id text attached_obj_id attached_obj_type])
render_success(data: data)
end
api :GET, '/repp/v1/registrar/notifications/:notification_id'
desc 'Get a specific poll message'
def show
@notification = current_user.registrar.notifications.find(params[:id])
data = @notification.as_json(only: %i[id text attached_obj_id attached_obj_type read])
render_success(data: data)
end
api :PUT, '/repp/v1/registrar/notifications'
desc 'Mark poll message as read'
param :notification, Hash, required: true do
param :read, [true], required: true, desc: 'Set as true to mark as read'
end
def update
# rubocop:disable Style/AndOr
handle_errors(@notification) and return unless @notification.mark_as_read
# rubocop:enable Style/AndOr
render_success(data: { notification_id: @notification.id, read: true })
end
private
def set_notification
@notification = current_user.unread_notifications.find(params[:id])
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Actions
class BaseAction
def self.maybe_attach_legal_doc(entity, legal_doc)
return unless legal_doc
return if legal_doc[:body].starts_with?(ENV['legal_documents_dir'])
entity.legal_documents.create(
document_type: legal_doc[:type],
body: legal_doc[:body]
)
end
def self.attach_legal_doc_to_new(entity, legal_doc, domain: true)
return unless legal_doc
doc = LegalDocument.create(
documentable_type: domain ? Domain : Contact,
document_type: legal_doc[:type],
body: legal_doc[:body]
)
entity.legal_documents = [doc]
end
end
end

View file

@ -59,15 +59,7 @@ module Actions
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
::Actions::BaseAction.attach_legal_doc_to_new(contact, legal_document, domain: false)
end
def commit

View file

@ -28,15 +28,7 @@ module Actions
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
::Actions::BaseAction.maybe_attach_legal_doc(contact, legal_document)
end
def commit

View file

@ -42,14 +42,7 @@ module Actions
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
::Actions::BaseAction.maybe_attach_legal_doc(contact, legal_document)
end
def maybe_update_ident

View file

@ -0,0 +1,214 @@
module Actions
class DomainCreate # rubocop:disable Metrics/ClassLength
attr_reader :domain, :params
def initialize(domain, params)
@domain = domain
@params = params
end
def call
assign_domain_attributes
validate_domain_integrity
return false if domain.errors[:epp_errors].any?
assign_registrant
assign_nameservers
assign_domain_contacts
domain.attach_default_contacts
assign_expiry_time
maybe_attach_legal_doc
commit
end
def check_contact_duplications
if check_for_same_contacts(@admin_contacts, 'admin') &&
check_for_same_contacts(@tech_contacts, 'tech')
true
else
false
end
end
def check_for_same_contacts(contacts, contact_type)
return true unless contacts.uniq.count != contacts.count
domain.add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid])
false
end
# Check if domain is eligible for new registration
def validate_domain_integrity
return unless Domain.release_to_auction
dn = DNS::DomainName.new(domain.name)
if dn.at_auction?
domain.add_epp_error('2306', nil, nil, 'Parameter value policy error: domain is at auction')
elsif dn.awaiting_payment?
domain.add_epp_error('2003', nil, nil, 'Required parameter missing; reserved>pw element' \
' required for reserved domains')
elsif dn.pending_registration?
validate_reserved_password(dn)
end
end
def validate_reserved_password(domain_name)
if params[:reserved_pw].blank?
domain.add_epp_error('2003', nil, nil, 'Required parameter missing; reserved>pw ' \
'element is required')
else
unless domain_name.available_with_code?(params[:reserved_pw])
domain.add_epp_error('2202', nil, nil, 'Invalid authorization information; invalid ' \
'reserved>pw value')
end
end
end
def assign_registrant
unless params[:registrant]
domain.add_epp_error('2306', nil, nil, %i[registrant cannot_be_missing])
return
end
regt = Registrant.find_by(code: params[:registrant])
if regt
domain.registrant = regt
else
domain.add_epp_error('2303', 'registrant', params[:registrant], %i[registrant not_found])
end
end
def assign_domain_attributes
domain.name = params[:name].strip.downcase
domain.registrar = current_registrar
assign_domain_period
assign_domain_auth_codes
assign_dnskeys
end
def assign_dnskeys
return unless params[:dnskeys_attributes]&.any?
params[:dnskeys_attributes].each { |dk| verify_public_key_integrity(dk[:public_key]) }
domain.dnskeys_attributes = params[:dnskeys_attributes]
end
def verify_public_key_integrity(pub)
return if Dnskey.pub_key_base64?(pub)
domain.add_epp_error(2005, nil, nil, %i[dnskeys invalid])
end
def assign_domain_auth_codes
domain.transfer_code = params[:transfer_code] if params[:transfer_code].present?
domain.reserved_pw = params[:reserved_pw] if params[:reserved_pw].present?
end
def assign_domain_period
domain.period = params[:period]
domain.period_unit = params[:period_unit]
end
def assign_nameservers
return unless params[:nameservers_attributes]
domain.nameservers_attributes = params[:nameservers_attributes]
end
def assign_contact(contact_code, admin: true)
contact = Contact.find_by(code: contact_code)
arr = admin ? @admin_contacts : @tech_contacts
if contact
arr << { contact_id: contact.id, contact_code_cache: contact.code }
else
domain.add_epp_error('2303', 'contact', contact_code, %i[domain_contacts not_found])
end
end
def assign_domain_contacts
@admin_contacts = []
@tech_contacts = []
params[:admin_contacts]&.each { |c| assign_contact(c) }
params[:tech_contacts]&.each { |c| assign_contact(c, admin: false) }
domain.admin_domain_contacts_attributes = @admin_contacts
domain.tech_domain_contacts_attributes = @tech_contacts
check_contact_duplications
end
def assign_expiry_time
return unless domain.period
period = Integer(domain.period)
domain.expire_time = calculate_expiry(period)
end
def calculate_expiry(period)
plural_period_unit_name = (domain.period_unit == 'm' ? 'months' : 'years').to_sym
(Time.zone.now.advance(plural_period_unit_name => period) + 1.day).beginning_of_day
end
def action_billable?
unless domain_pricelist&.price
domain.add_epp_error(2104, nil, nil, I18n.t(:active_price_missing_for_this_operation))
return false
end
if domain.registrar.balance < domain_pricelist.price.amount
domain.add_epp_error(2104, nil, nil, I18n.t('billing_failure_credit_balance_low'))
return false
end
true
end
def debit_registrar
return unless action_billable?
domain.registrar.debit!(sum: domain_pricelist.price.amount, price: domain_pricelist,
description: "#{I18n.t('create')} #{domain.name}",
activity_type: AccountActivity::CREATE)
end
def domain_pricelist
@domain_pricelist ||= domain.pricelist('create', domain.period.try(:to_i), domain.period_unit)
@domain_pricelist
end
def maybe_attach_legal_doc
::Actions::BaseAction.attach_legal_doc_to_new(domain, params[:legal_document], domain: true)
end
def process_auction_and_disputes
dn = DNS::DomainName.new(domain.name)
Dispute.close_by_domain(domain.name)
return unless Domain.release_to_auction && dn.pending_registration?
Auction.find_by(domain: domain.name,
status: Auction.statuses[:payment_received])&.domain_registered!
end
def commit
return false if domain.errors[:epp_errors].any? || validation_process_errored?
debit_registrar
return false if domain.errors.any?
process_auction_and_disputes
domain.save
end
def validation_process_errored?
return if domain.valid?
domain.errors.delete(:name_dirty) if domain.errors[:puny_label].any?
domain.errors.any?
end
def current_registrar
Registrar.find(params[:registrar])
end
end
end

View file

@ -0,0 +1,57 @@
module Actions
class DomainDelete
attr_reader :domain
attr_reader :params
attr_reader :user
def initialize(domain, params, user)
@domain = domain
@params = params
@user = user
end
def call
return false unless @domain.can_be_deleted?
verify_not_discarded
maybe_attach_legal_doc
return false if domain.errors.any?
return false if domain.errors[:epp_errors].any?
destroy
end
def maybe_attach_legal_doc
::Actions::BaseAction.attach_legal_doc_to_new(domain, params[:legal_document], domain: true)
end
def verify_not_discarded
return unless domain.discarded?
domain.add_epp_error('2304', nil, nil, 'Object status prohibits operation')
end
def verify?
return false unless Setting.request_confirmation_on_domain_deletion_enabled
return false if params[:delete][:verified] == true
true
end
def ask_delete_verification
domain.registrant_verification_asked!(params, user.id)
domain.pending_delete!
domain.manage_automatic_statuses
end
def destroy
if verify?
ask_delete_verification
else
domain.set_pending_delete!
end
true
end
end
end

View file

@ -0,0 +1,34 @@
module Actions
class DomainRenew
attr_reader :domain
attr_reader :params
attr_reader :user
def initialize(domain, params, user)
@domain = domain
@params = params
@user = user
end
def call
domain.is_renewal = true
if !domain.renewable? || domain.invalid?
domain.add_renew_epp_errors
false
else
domain.validate_exp_dates(params[:exp_date])
renew
end
end
def renew
return false if domain.errors[:epp_errors].any?
task = Domains::BulkRenew::SingleDomainRenew.run(domain: domain,
period: params[:period],
unit: params[:period_unit],
registrar: user)
task.valid?
end
end
end

View file

@ -0,0 +1,256 @@
module Actions
class DomainUpdate # rubocop:disable Metrics/ClassLength
attr_reader :domain, :params, :bypass_verify
def initialize(domain, params, bypass_verify)
@domain = domain
@params = params
@bypass_verify = bypass_verify
@changes_registrant = false
end
def call
validate_domain_integrity
assign_new_registrant if params[:registrant]
assign_relational_modifications
assign_requested_statuses
::Actions::BaseAction.maybe_attach_legal_doc(domain, params[:legal_document])
commit
end
def assign_relational_modifications
assign_nameserver_modifications if params[:nameservers]
assign_dnssec_modifications if params[:dns_keys]
return unless params[:contacts]
assign_admin_contact_changes
assign_tech_contact_changes
end
def check_for_same_contacts(contacts, contact_type)
return unless contacts.uniq.count != contacts.count
domain.add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid])
end
def validate_domain_integrity
domain.auth_info = params[:transfer_code] if params[:transfer_code]
return unless domain.discarded?
domain.add_epp_error('2304', nil, nil, 'Object status prohibits operation')
end
def assign_new_registrant
unless params[:registrant][:code]
domain.add_epp_error('2306', nil, nil, %i[registrant cannot_be_missing])
end
regt = Registrant.find_by(code: params[:registrant][:code])
unless regt
domain.add_epp_error('2303', 'registrant', params[:registrant], %i[registrant not_found])
return
end
replace_domain_registrant(regt)
end
def replace_domain_registrant(new_registrant)
return if domain.registrant == new_registrant
@changes_registrant = true if domain.registrant.ident != new_registrant.ident
if @changes_registrant && domain.registrant_change_prohibited?
domain.add_epp_error(2304, 'status', DomainStatus::SERVER_REGISTRANT_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation))
else
domain.registrant = new_registrant
end
end
def assign_nameserver_modifications
@nameservers = []
params[:nameservers].each do |ns_attr|
case ns_attr[:action]
when 'rem'
validate_ns_integrity(ns_attr)
when 'add'
@nameservers << ns_attr.except(:action)
end
end
domain.nameservers_attributes = @nameservers if @nameservers.present?
end
def validate_ns_integrity(ns_attr)
ns = domain.nameservers.from_hash_params(ns_attr.except(:action)).first
if ns
@nameservers << { id: ns.id, _destroy: 1 }
else
domain.add_epp_error('2303', 'hostAttr', ns_attr[:hostname], %i[nameservers not_found])
end
end
def assign_dnssec_modifications
@dnskeys = []
params[:dns_keys].each do |key|
case key[:action]
when 'add'
validate_dnskey_integrity(key)
when 'rem'
assign_removable_dnskey(key)
end
end
domain.dnskeys_attributes = @dnskeys.uniq
end
def validate_dnskey_integrity(key)
if key[:public_key] && !Setting.key_data_allowed
domain.add_epp_error('2306', nil, nil, %i[dnskeys key_data_not_allowed])
elsif Dnskey.pub_key_base64?(key[:public_key])
@dnskeys << key.except(:action)
else
domain.add_epp_error(2005, nil, nil, %i[dnskeys invalid])
end
end
def assign_removable_dnskey(key)
dnkey = domain.dnskeys.find_by(key.except(:action))
domain.add_epp_error(2303, nil, nil, %i[dnskeys not_found]) unless dnkey
@dnskeys << { id: dnkey.id, _destroy: 1 } if dnkey
end
def assign_admin_contact_changes
props = gather_domain_contacts(params[:contacts].select { |c| c[:type] == 'admin' })
if props.any? && domain.admin_change_prohibited?
domain.add_epp_error('2304', 'admin', DomainStatus::SERVER_ADMIN_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation))
elsif props.present?
domain.admin_domain_contacts_attributes = props
check_for_same_contacts(props, 'admin')
end
end
def assign_tech_contact_changes
props = gather_domain_contacts(params[:contacts].select { |c| c[:type] == 'tech' },
admin: false)
if props.any? && domain.tech_change_prohibited?
domain.add_epp_error('2304', 'tech', DomainStatus::SERVER_TECH_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation))
elsif props.present?
domain.tech_domain_contacts_attributes = props
check_for_same_contacts(props, 'tech')
end
end
def gather_domain_contacts(contacts, admin: true)
props = []
contacts.each do |c|
contact = contact_for_action(action: c[:action], method: admin ? 'admin' : 'tech',
code: c[:code])
entry = assign_contact(contact, add: c[:action] == 'add', admin: admin, code: c[:code])
props << entry if entry.is_a?(Hash)
end
props
end
def contact_for_action(action:, method:, code:)
contact = Epp::Contact.find_by(code: code)
return contact if action == 'add' || !contact
return domain.admin_domain_contacts.find_by(contact_id: contact.id) if method == 'admin'
domain.tech_domain_contacts.find_by(contact_id: contact.id)
end
def assign_contact(obj, add: false, admin: true, code:)
if obj.blank?
domain.add_epp_error('2303', 'contact', code, %i[domain_contacts not_found])
elsif obj.try(:org?) && admin && add
domain.add_epp_error('2306', 'contact', code,
%i[domain_contacts admin_contact_can_be_only_private_person])
else
add ? { contact_id: obj.id, contact_code_cache: obj.code } : { id: obj.id, _destroy: 1 }
end
end
def assign_requested_statuses
return unless params[:statuses]
@rem = []
@add = []
@failed = false
params[:statuses].each { |s| verify_status_eligiblity(s) }
domain.statuses = (domain.statuses - @rem + @add) unless @failed
end
def verify_status_eligiblity(status_entry)
status, action = status_entry.select_keys(:status, :action)
return unless permitted_status?(status, action)
action == 'add' ? @add << status : @rem << status
end
def permitted_status?(status, action)
if DomainStatus::CLIENT_STATUSES.include?(status) &&
(domain.statuses.include?(status) || action == 'add')
return true
end
domain.add_epp_error('2303', 'status', status, %i[statuses not_found])
@failed = true
false
end
def verify_registrant_change?
return if !@changes_registrant || params[:registrant][:verified] == true
return true unless domain.disputed?
return validate_dispute_case if params[:reserved_pw]
domain.add_epp_error('2304', nil, nil, 'Required parameter missing; reservedpw element ' \
'required for dispute domains')
true
end
def validate_dispute_case
dispute = Dispute.active.find_by(domain_name: domain.name, password: params[:reserved_pw])
if dispute
Dispute.close_by_domain(domain.name)
false
else
domain.add_epp_error('2202', nil, nil,
'Invalid authorization information; invalid reserved>pw value')
true
end
end
def ask_registrant_verification
if verify_registrant_change? && !bypass_verify &&
Setting.request_confirmation_on_registrant_change_enabled
domain.registrant_verification_asked!(params, params[:registrar])
end
end
def commit
return false if any_errors?
ask_registrant_verification
return false if any_errors?
domain.save
end
def any_errors?
return true if domain.errors[:epp_errors].any? || domain.invalid?
false
end
end
end

View file

@ -9,6 +9,7 @@ module Domains
def execute
in_transaction_with_retries do
check_balance
success = domain.renew(domain.valid_to, period, unit)
if success
check_balance
@ -55,6 +56,7 @@ module Domains
private
def add_error
domain.add_epp_error(2104, nil, nil, I18n.t(:domain_renew_error_for_domain))
errors.add(:domain, I18n.t('domain_renew_error_for_domain', domain: domain.name))
end
end

View file

@ -9,15 +9,30 @@ module Domains
string :unit
def execute
return domain_pricelist.price.amount if domain_pricelist.try(:price)
if domain_pricelist.try(:price)
price = domain_pricelist.price.amount
return price if balance_ok?(price)
else
domain.add_epp_error(2104, nil, nil, I18n.t(:active_price_missing_for_this_operation))
errors.add(:domain, I18n.t(:active_price_missing_for_operation_with_domain,
domain: domain.name))
end
errors.add(:domain, I18n.t(:active_price_missing_for_operation_with_domain,
domain: domain.name))
false
end
private
def balance_ok?(price)
if domain.registrar.cash_account.balance >= price
true
else
domain.add_epp_error(2104, nil, nil, I18n.t(:not_enough_funds))
errors.add(:domain, I18n.t(:billing_failure_credit_balance_low, domain: domain.name))
false
end
end
def domain_pricelist
domain.pricelist(operation, period.try(:to_i), unit)
end

View file

@ -20,15 +20,20 @@ module Domains
WhoisRecord.find_by(domain_id: domain.id).save # need to reload model
end
# rubocop:disable Metrics/AbcSize
def update_domain
frame_json = domain.pending_json['frame']
frame = frame_json ? frame_json.with_indifferent_access : {}
assign_domain_update_meta
Actions::DomainUpdate.new(domain, frame, true).call
end
def assign_domain_update_meta
user = ApiUser.find(domain.pending_json['current_user_id'])
frame = Nokogiri::XML(domain.pending_json['frame'])
domain.upid = user.registrar.id if user.registrar
domain.up_date = Time.zone.now
domain.update(frame, user, false)
end
# rubocop:enable Metrics/AbcSize
end
end
end

View file

@ -60,7 +60,10 @@ module Billing
def self.price_for(zone, operation_category, duration)
lists = valid.where(zone: zone, operation_category: operation_category, duration: duration)
return lists.first if lists.count == 1
lists.order(valid_from: :desc).first
rescue ActiveRecord::StatementInvalid
nil
end
def name

View file

@ -86,6 +86,8 @@ module EppErrors
end
end
nil
rescue NameError
nil
end
def construct_msg_args_and_value(epp_error_args)

View file

@ -8,6 +8,7 @@ class Dnskey < ApplicationRecord
validate :validate_algorithm
validate :validate_protocol
validate :validate_flags
validate :validate_public_key
before_save lambda {
generate_digest if will_save_change_to_public_key? && !will_save_change_to_ds_digest?
@ -115,6 +116,12 @@ class Dnskey < ApplicationRecord
self.ds_key_tag = ((c & 0xFFFF) + (c >> 16)) & 0xFFFF
end
def validate_public_key
return if Dnskey.pub_key_base64?(public_key)
errors.add(:public_key, :invalid)
end
class << self
def int_to_hex(s)
s = s.to_s(16)
@ -128,5 +135,13 @@ class Dnskey < ApplicationRecord
def bin_to_hex(s)
s.each_byte.map { |b| format('%02X', b) }.join
end
def pub_key_base64?(pub)
return unless pub&.is_a?(String)
Base64.strict_encode64(Base64.strict_decode64(pub)) == pub
rescue ArgumentError
false
end
end
end

View file

@ -513,7 +513,7 @@ class Domain < ApplicationRecord
# depricated not used, not valid
def update_prohibited?
pending_update_prohibited? && pending_delete_prohibited?
(statuses & DomainStatus::UPDATE_PROHIBIT_STATES).present?
end
# public api

View file

@ -1,5 +1,7 @@
require 'deserializers/xml/legal_document'
require 'deserializers/xml/nameserver'
require 'deserializers/xml/domain_create'
require 'deserializers/xml/domain_update'
class Epp::Domain < Domain
include EppErrors
@ -27,30 +29,15 @@ class Epp::Domain < Domain
active_techs = tech_domain_contacts.select { |x| !x.marked_for_destruction? }
# validate registrant here as well
([Contact.find_by(code: registrant.code)] + active_admins + active_techs).each do |x|
([Contact.find(registrant.id)] + active_admins + active_techs).each do |x|
unless x.valid?
add_epp_error('2304', nil, nil, I18n.t(:contact_is_not_valid, value: x.code))
add_epp_error('2304', nil, nil, I18n.t(:contact_is_not_valid, value: x.try(:code)))
ok = false
end
end
ok
end
class << self
def new_from_epp(frame, current_user)
domain = Epp::Domain.new
domain.attributes = domain.attrs_from(frame, current_user)
domain.attach_default_contacts
period = domain.period.to_i
plural_period_unit_name = (domain.period_unit == 'm' ? 'months' : 'years').to_sym
expire_time = (Time.zone.now.advance(plural_period_unit_name => period) + 1.day).beginning_of_day
domain.expire_time = expire_time
domain
end
end
def epp_code_map
{
'2002' => [ # Command use error
@ -123,408 +110,10 @@ class Epp::Domain < Domain
def attach_default_contacts
return if registrant.blank?
tech_contacts << registrant if tech_domain_contacts.blank?
admin_contacts << registrant if admin_domain_contacts.blank? && !registrant.org?
end
registrant_obj = Contact.find_by(code: registrant.code)
def attrs_from(frame, current_user, action = nil)
at = {}.with_indifferent_access
registrant_frame = frame.css('registrant').first
code = registrant_frame.try(:text)
if code.present?
if action == 'chg' && registrant_change_prohibited?
add_epp_error('2304', "status", DomainStatus::SERVER_REGISTRANT_CHANGE_PROHIBITED, I18n.t(:object_status_prohibits_operation))
end
regt = Registrant.find_by(code: code)
if regt
at[:registrant_id] = regt.id
else
add_epp_error('2303', 'registrant', code, [:registrant, :not_found])
end
else
add_epp_error('2306', nil, nil, [:registrant, :cannot_be_missing])
end if registrant_frame
at[:name] = frame.css('name').text if new_record?
at[:registrar_id] = current_user.registrar.try(:id)
period = frame.css('period').text
at[:period] = (period.to_i == 0) ? 1 : period.to_i
at[:period_unit] = Epp::Domain.parse_period_unit_from_frame(frame) || 'y'
at[:reserved_pw] = frame.css('reserved > pw').text
# at[:statuses] = domain_statuses_attrs(frame, action)
at[:nameservers_attributes] = nameservers_attrs(frame, action)
at[:admin_domain_contacts_attributes] = admin_domain_contacts_attrs(frame, action)
at[:tech_domain_contacts_attributes] = tech_domain_contacts_attrs(frame, action)
check_for_same_contacts(at[:admin_domain_contacts_attributes], 'admin')
check_for_same_contacts(at[:tech_domain_contacts_attributes], 'tech')
pw = frame.css('authInfo > pw').text
at[:transfer_code] = pw if pw.present?
if new_record?
dnskey_frame = frame.css('extension create')
else
dnskey_frame = frame
end
at[:dnskeys_attributes] = dnskeys_attrs(dnskey_frame, action)
at
end
def check_for_same_contacts(contacts, contact_type)
return unless contacts.uniq.count != contacts.count
add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid])
end
# Adding legal doc to domain and
# if something goes wrong - raise Rollback error
def add_legal_file_to_new frame
legal_document_data = ::Deserializers::Xml::LegalDocument.new(frame).call
return unless legal_document_data
return if legal_document_data[:body].starts_with?(ENV['legal_documents_dir'])
doc = LegalDocument.create(documentable_type: Domain, document_type: legal_document_data[:type],
body: legal_document_data[:body])
self.legal_documents = [doc]
frame.css("legalDocument").first.content = doc.path if doc&.persisted?
self.legal_document_id = doc.id
end
def nameservers_attrs(frame, action)
ns_list = nameservers_from(frame)
if action == 'rem'
to_destroy = []
ns_list.each do |ns_attrs|
nameserver = nameservers.find_by_hash_params(ns_attrs).first
if nameserver.blank?
add_epp_error('2303', 'hostAttr', ns_attrs[:hostname], [:nameservers, :not_found])
else
to_destroy << {
id: nameserver.id,
_destroy: 1
}
end
end
return to_destroy
else
return ns_list
end
end
def nameservers_from(frame)
res = []
frame.css('hostAttr').each do |x|
host_attr = {
hostname: x.css('hostName').first.try(:text),
ipv4: x.css('hostAddr[ip="v4"]').map(&:text).compact,
ipv6: x.css('hostAddr[ip="v6"]').map(&:text).compact
}
res << host_attr.delete_if { |_k, v| v.blank? }
end
res
end
def admin_domain_contacts_attrs(frame, action)
admin_attrs = domain_contact_attrs_from(frame, action, 'admin')
if admin_attrs.present? && admin_change_prohibited?
add_epp_error('2304', 'admin', DomainStatus::SERVER_ADMIN_CHANGE_PROHIBITED, I18n.t(:object_status_prohibits_operation))
return []
end
case action
when 'rem'
return destroy_attrs(admin_attrs, admin_domain_contacts)
else
return admin_attrs
end
end
def tech_domain_contacts_attrs(frame, action)
tech_attrs = domain_contact_attrs_from(frame, action, 'tech')
if tech_attrs.present? && tech_change_prohibited?
add_epp_error('2304', 'tech', DomainStatus::SERVER_TECH_CHANGE_PROHIBITED, I18n.t(:object_status_prohibits_operation))
return []
end
case action
when 'rem'
return destroy_attrs(tech_attrs, tech_domain_contacts)
else
return tech_attrs
end
end
def destroy_attrs(attrs, dcontacts)
destroy_attrs = []
attrs.each do |at|
domain_contact_id = dcontacts.find_by(contact_id: at[:contact_id]).try(:id)
unless domain_contact_id
add_epp_error('2303', 'contact', at[:contact_code_cache], [:domain_contacts, :not_found])
next
end
destroy_attrs << {
id: domain_contact_id,
_destroy: 1
}
end
destroy_attrs
end
def domain_contact_attrs_from(frame, action, type)
attrs = []
frame.css('contact').each do |x|
next if x['type'] != type
c = Epp::Contact.find_by_epp_code(x.text)
unless c
add_epp_error('2303', 'contact', x.text, [:domain_contacts, :not_found])
next
end
if action != 'rem'
if x['type'] == 'admin' && c.org?
add_epp_error('2306', 'contact', x.text, [:domain_contacts, :admin_contact_can_be_only_private_person])
next
end
end
attrs << {
contact_id: c.id,
contact_code_cache: c.code
}
end
attrs
end
def dnskeys_attrs(frame, action)
keys = []
return keys if frame.blank?
inf_data = DnsSecKeys.new(frame)
add_epp_error('2005', nil, nil, %i[dnskeys invalid]) if not_base64?(inf_data)
if action == 'rem' &&
frame.css('rem > all').first.try(:text) == 'true'
keys = inf_data.mark_destroy_all dnskeys
else
if Setting.key_data_allowed
errors.add(:base, :ds_data_not_allowed) if inf_data.ds_data.present?
keys = inf_data.key_data
end
if Setting.ds_data_allowed
errors.add(:base, :key_data_not_allowed) if inf_data.key_data.present?
keys = inf_data.ds_data
end
if action == 'rem'
keys = inf_data.mark_destroy(dnskeys)
add_epp_error('2303', nil, nil, [:dnskeys, :not_found]) if keys.include? nil
end
end
errors.any? ? [] : keys
end
def not_base64?(inf_data)
inf_data.key_data.any? do |key|
value = key[:public_key]
!value.is_a?(String) || Base64.strict_encode64(Base64.strict_decode64(value)) != value
end
rescue ArgumentError
true
end
class DnsSecKeys
def initialize(frame)
@key_data = []
@ds_data = []
# schema validation prevents both in the same parent node
if frame.css('dsData').present?
ds_data_from frame
else
frame.css('keyData').each do |key|
@key_data.append key_data_from(key)
end
end
end
attr_reader :key_data
attr_reader :ds_data
def mark_destroy_all(dns_keys)
# if transition support required mark_destroy dns_keys when has ds/key values otherwise ...
dns_keys.map { |inf_data| mark inf_data }
end
def mark_destroy(dns_keys)
(ds_data.present? ? ds_filter(dns_keys) : kd_filter(dns_keys)).map do |inf_data|
inf_data.blank? ? nil : mark(inf_data)
end
end
private
KEY_INTERFACE = {flags: 'flags', protocol: 'protocol', alg: 'alg', public_key: 'pubKey' }
DS_INTERFACE =
{ ds_key_tag: 'keyTag',
ds_alg: 'alg',
ds_digest_type: 'digestType',
ds_digest: 'digest'
}
def xm_copy(frame, map)
result = {}
map.each do |key, elem|
result[key] = frame.css(elem).first.try(:text)
end
result
end
def key_data_from(frame)
xm_copy frame, KEY_INTERFACE
end
def ds_data_from(frame)
frame.css('dsData').each do |ds_data|
key = ds_data.css('keyData')
ds = xm_copy ds_data, DS_INTERFACE
ds.merge(key_data_from key) if key.present?
@ds_data << ds
end
end
def ds_filter(dns_keys)
@ds_data.map do |ds|
dns_keys.find_by(ds.slice(*DS_INTERFACE.keys))
end
end
def kd_filter(dns_keys)
@key_data.map do |key|
dns_keys.find_by(key)
end
end
def mark(inf_data)
{ id: inf_data.id, _destroy: 1 }
end
end
def domain_statuses_attrs(frame, action)
status_list = domain_status_list_from(frame)
if action == 'rem'
to_destroy = []
status_list.each do |x|
if statuses.include?(x)
to_destroy << x
else
add_epp_error('2303', 'status', x, %i[statuses not_found])
end
end
return to_destroy
else
return status_list
end
end
def domain_status_list_from(frame)
status_list = []
frame.css('status').each do |x|
unless DomainStatus::CLIENT_STATUSES.include?(x['s'])
add_epp_error('2303', 'status', x['s'], %i[statuses not_found])
next
end
status_list << x['s']
end
status_list
end
def update(frame, current_user, verify = true)
return super if frame.blank?
if discarded? || statuses_blocks_update?
add_epp_error('2304', nil, nil, 'Object status prohibits operation')
return
end
at = {}.with_indifferent_access
at.deep_merge!(attrs_from(frame.css('chg'), current_user, 'chg'))
at.deep_merge!(attrs_from(frame.css('rem'), current_user, 'rem'))
if doc = attach_legal_document(::Deserializers::Xml::LegalDocument.new(frame).call)
frame.css("legalDocument").first.content = doc.path if doc&.persisted?
self.legal_document_id = doc.id
end
at_add = attrs_from(frame.css('add'), current_user, 'add')
at[:nameservers_attributes] += at_add[:nameservers_attributes]
at[:admin_domain_contacts_attributes] += at_add[:admin_domain_contacts_attributes]
at[:tech_domain_contacts_attributes] += at_add[:tech_domain_contacts_attributes]
at[:dnskeys_attributes] += at_add[:dnskeys_attributes]
at[:statuses] =
statuses - domain_statuses_attrs(frame.css('rem'), 'rem') + domain_statuses_attrs(frame.css('add'), 'add')
if errors.empty? && verify
self.upid = current_user.registrar.id if current_user.registrar
self.up_date = Time.zone.now
end
registrant_verification_needed = false
# registrant block may not be present, so we need this to rule out false positives
if frame.css('registrant').text.present?
registrant_verification_needed = verification_needed?(code: frame.css('registrant').text)
end
if registrant_verification_needed && disputed?
disputed_pw = frame.css('reserved > pw').text
if disputed_pw.blank?
add_epp_error('2304', nil, nil, 'Required parameter missing; reserved' \
'pw element required for dispute domains')
else
dispute = Dispute.active.find_by(domain_name: name, password: disputed_pw)
if dispute
Dispute.close_by_domain(name)
registrant_verification_needed = false # Prevent asking current registrant confirmation
else
add_epp_error('2202', nil, nil, 'Invalid authorization information; '\
'invalid reserved>pw value')
end
end
end
unverified_registrant_params = frame.css('registrant').present? &&
frame.css('registrant').attr('verified').to_s.downcase != 'yes'
if registrant_verification_needed && errors.empty? && verify &&
Setting.request_confirmation_on_registrant_change_enabled &&
unverified_registrant_params
registrant_verification_asked!(frame.to_s, current_user.id) unless disputed?
end
errors.empty? && super(at)
tech_contacts << registrant_obj if tech_domain_contacts.blank?
admin_contacts << registrant_obj if admin_domain_contacts.blank? && !registrant.org?
end
def apply_pending_delete!
@ -618,7 +207,7 @@ class Epp::Domain < Domain
end
def add_renew_epp_errors
if renew_blocking_statuses.any? && !renewable?
if renew_blocking_statuses.any? || !renewable?
add_epp_error('2304', 'status', renew_blocking_statuses,
I18n.t('object_status_prohibits_operation'))
end

View file

@ -63,7 +63,7 @@ class Nameserver < ApplicationRecord
end
class << self
def find_by_hash_params params
def from_hash_params params
params = params.with_indifferent_access
rel = all
rel = rel.where(hostname: params[:hostname])

View file

@ -6,7 +6,7 @@ class TechDomainContact < DomainContact
tech_contacts = where(contact: current_contact)
tech_contacts.each do |tech_contact|
if tech_contact.domain.bulk_update_prohibited?
if irreplaceable?(tech_contact)
skipped_domains << tech_contact.domain.name
next
end
@ -20,4 +20,9 @@ class TechDomainContact < DomainContact
end
[affected_domains.sort, skipped_domains.sort]
end
def self.irreplaceable?(tech_contact)
dn = tech_contact.domain
dn.bulk_update_prohibited? || dn.update_prohibited? || dn.tech_change_prohibited?
end
end