From ccef1053d9fde039b4bfbce41deb86f3d58027a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Erik=20=C3=95unapuu?= Date: Tue, 8 Dec 2020 12:00:04 +0200 Subject: [PATCH] Create DomainCreate action --- app/controllers/epp/domains_controller.rb | 4 + app/models/actions/domain_create.rb | 134 ++++++++++++++++++++++ app/models/epp/domain.rb | 28 ++--- lib/deserializers/xml/dnssec.rb | 89 ++++++++++++++ lib/deserializers/xml/domain.rb | 11 +- lib/deserializers/xml/domain_create.rb | 42 +++++-- 6 files changed, 280 insertions(+), 28 deletions(-) create mode 100644 app/models/actions/domain_create.rb create mode 100644 lib/deserializers/xml/dnssec.rb diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb index d9a8b2b5d..9e21650cf 100644 --- a/app/controllers/epp/domains_controller.rb +++ b/app/controllers/epp/domains_controller.rb @@ -28,6 +28,10 @@ module Epp def create authorize! :create, Epp::Domain + domain_params = ::Deserializers::Xml::DomainCreate.new(params[:parsed_frame], current_user.registrar.id) + puts "Ayy lmao" + puts domain_params.call + 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)) diff --git a/app/models/actions/domain_create.rb b/app/models/actions/domain_create.rb new file mode 100644 index 000000000..2dd2b7d2a --- /dev/null +++ b/app/models/actions/domain_create.rb @@ -0,0 +1,134 @@ +module Actions + class DomainCreate + attr_reader :domain, :params + + def initialize(domain, params) + @domain = domain + @params = params + end + + def call + validate_domain_integrity + assign_registrant + assign_domain_attributes + assign_nameservers + assign_admin_contacts + assign_tech_contacts + assign_expiry_time + + return domain unless domain.save + + commit + end + + # Check if domain is eligible for new registration + def validate_domain_integrity + return if Domain.release_to_auction + + dn = DNS::DomainName.new(SimpleIDN.to_unicode(params[:name])) + domain.add_epp_error(2306, nil, nil, 'Parameter value policy error: domain is at auction') if dn.at_auction? + domain.add_epp_error(2003, nil, nil, 'Required parameter missing; reserved>pw element required for reserved domains') if dn.awaiting_payment? + return unless dn.pending_registration? + + domain.add_epp_error(2003, nil, nil, 'Required parameter missing; reserved>pw element is required') if params[:reserved_pw].empty? + domain.add_epp_errpr(2202, nil, nil, 'Invalid authorization information; invalid reserved>pw value') unless dn.available_with_code?(params[:reserved_pw]) + end + + def assign_registrant + domain.add_epp_error('2306', nil, nil, %i[registrant cannot_be_missing]) and return unless params[:registrant_id] + + regt = Registrant.find_by(code: params[:registrant_id]) + domain.add_epp_error('2303', 'registrant', code, %i[registrant not_found]) and return unless regt + + domain.registrant = regt + end + + def assign_domain_attributes + domain.name = params[:name] + domain.registrar = Registrar.find(params[:registrar_id]) + domain.period = params[:period] + domain.period_unit = params[:period_unit] + domain.reserved_pw = params[:reserved_pw] if params[:transfer_code] + domain.transfer_code = params[:transfer_code] if params[:transfer_code] + domain.dnskeys_attributes = params[:dnskeys_attributes] + end + + def assign_nameservers + domain.nameservers_attributes = params[:nameservers_attributes] + end + + def assign_admin_contacts + attrs = [] + params[:admin_domain_contacts_attributes].each do |c| + contact = Contact.find_by(code: c) + domain.add_epp_error('2303', 'contact', c, %i[domain_contacts not_found]) unless contact + attrs << { contact_id: contact.id, contact_code_cache: contact.code } if contact + end + + domain.admin_domain_contacts_attributes = attrs + end + + def assign_tech_contacts + attrs = [] + params[:tech_domain_contacts_attributes].each do |c| + contact = Contact.find_by(code: c) + domain.add_epp_error('2303', 'contact', c, %i[domain_contacts not_found]) unless contact + attrs << { contact_id: contact.id, contact_code_cache: contact.code } if contact + end + + domain.tech_domain_contacts_attributes = attrs + end + + def assign_expiry_time + 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 + end + + def debit_registrar + domain_pricelist = domain.pricelist('create', domain.period.try(:to_i), domain.period_unit) + if @domain_pricelist.try(:price) && domain.registrar.balance < domain_pricelist.price.amount + domain.add_epp_error(2104, nil, nil, I18n.t('billing_failure_credit_balance_low')) + return + elsif !@domain_pricelist.try(:price) + domain.add_epp_error(2104, nil, nil, I18n.t(:active_price_missing_for_this_operation)) + return + end + + domain.registrar.debit!({ sum: @domain_pricelist.price.amount, price: @domain_pricelist, + description: "#{I18n.t('create')} #{@domain.name}", + activity_type: AccountActivity::CREATE }) + end + + def process_auction_and_disputes + dn = DNS::DomainName.new(SimpleIDN.to_unicode(domain.name)) + Dispute.close_by_domain(domain.name) + return unless Domain.release_to_auction && dn.pending_registration? + + auction = Auction.find_by(domain: domain.name, status: Auction.statuses[:payment_received]) + auction.domain_registered! + 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 + ActiveRecord::Base.transaction do + debit_registrar + process_auction_and_disputes + + domain.save + end + end + end +end diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb index d56fe4e32..2d5718b43 100644 --- a/app/models/epp/domain.rb +++ b/app/models/epp/domain.rb @@ -1,5 +1,6 @@ require 'deserializers/xml/legal_document' require 'deserializers/xml/nameserver' +require 'deserializers/xml/domain_create' class Epp::Domain < Domain include EppErrors @@ -38,6 +39,10 @@ class Epp::Domain < Domain class << self def new_from_epp(frame, current_user) + domain_create = ::Deserializers::Xml::DomainCreate.new(frame, current_user.registrar.id) + puts "DOMAIN CREATE ACTION" + puts domain_create + domain = Epp::Domain.new domain.attributes = domain.attrs_from(frame, current_user) domain.attach_default_contacts @@ -147,23 +152,19 @@ class Epp::Domain < Domain 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[:name] = frame.css('name').text if new_record? # Done + at[:registrar_id] = current_user.registrar.try(:id) # Done + period = frame.css('period').text # Done + at[:period] = (period.to_i == 0) ? 1 : period.to_i # Done + at[:period_unit] = Epp::Domain.parse_period_unit_from_frame(frame) || 'y' # Done + at[:reserved_pw] = frame.css('reserved > pw').text # Done + pw = frame.css('authInfo > pw').text # Done + at[:transfer_code] = pw if pw.present? # Done # 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) - puts "JHDFHJDGFKDJHF" - pw = frame.css('authInfo > pw').text - at[:transfer_code] = pw if pw.present? if new_record? dnskey_frame = frame.css('extension create') @@ -315,6 +316,7 @@ class Epp::Domain < Domain add_epp_error('2303', nil, nil, [:dnskeys, :not_found]) if keys.include? nil end end + errors.any? ? [] : keys end diff --git a/lib/deserializers/xml/dnssec.rb b/lib/deserializers/xml/dnssec.rb new file mode 100644 index 000000000..203e13197 --- /dev/null +++ b/lib/deserializers/xml/dnssec.rb @@ -0,0 +1,89 @@ +module Deserializers + module Xml + class DnssecKey + attr_reader :frame, :dsa + + 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 initialize(frame, dsa) + @frame = frame + @dsa = dsa + end + + def call + dsa ? ds_alg_output : xm_copy(frame, KEY_INTERFACE) + end + + def ds_alg_output + ds_key = xm_copy(frame, DS_INTERFACE) + ds_key.merge(xm_copy(frame.css('keyData'), KEY_INTERFACE)) if frame.css('keyData').present? + ds_key + end + + def other_alg_output + xm_copy(frame, KEY_INTERFACE) + end + + private + + def xm_copy(entry, map) + result = {} + map.each do |key, elem| + result[key] = entry.css(elem).first.try(:text) + end + result + end + end + + class DnssecKeys + attr_reader :frame, :key_data, :ds_data + + def initialize(frame) + @key_data = [] + @ds_data = [] + + # schema validation prevents both in the same parent node + if frame.css('dsData').present? + frame.css('dsData').each do |ds_data| + @ds_data << Deserializers::Xml::DnssecKey.new(ds_data, true).call + end + else + frame.css('keyData').each do |key| + @key_data << Deserializers::Xml::DnssecKey.new(key, false).call + end + end + end + + 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 + + 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 + end +end diff --git a/lib/deserializers/xml/domain.rb b/lib/deserializers/xml/domain.rb index f9f893313..74683761c 100644 --- a/lib/deserializers/xml/domain.rb +++ b/lib/deserializers/xml/domain.rb @@ -2,18 +2,21 @@ module Deserializers module Xml class Domain attr_reader :frame + attr_reader :registrar - def initialize(frame) + def initialize(frame, registrar) @frame = frame + @registrar = registrar end def call attributes = { name: if_present('name'), - registrar_id: current_user.registrar.id, + registrar_id: registrar, + registrant_id: if_present('registrant'), reserved_pw: if_present('reserved > pw'), - period: Integer(frame.css('period').text, 1), - period_unit: parsed_frame.css('period').first ? parsed_frame.css('period').first[:unit] : 'y' + period: Integer(frame.css('period').text) || 1, + period_unit: frame.css('period').first ? frame.css('period').first[:unit] : 'y', } pw = frame.css('authInfo > pw').text diff --git a/lib/deserializers/xml/domain_create.rb b/lib/deserializers/xml/domain_create.rb index b3bc6fe4a..51a98bd23 100644 --- a/lib/deserializers/xml/domain_create.rb +++ b/lib/deserializers/xml/domain_create.rb @@ -1,26 +1,46 @@ require 'deserializers/xml/legal_document' -require 'deserializers/xml/ident' -require 'deserializers/xml/contact' - +require 'deserializers/xml/domain' +require 'deserializers/xml/nameserver' +require 'deserializers/xml/dnssec' module Deserializers module Xml - class ContactUpdate + class DomainCreate attr_reader :frame + attr_reader :registrar - def initialize(frame) + def initialize(frame, registrar) @frame = frame + @registrar = registrar end - def contact - @contact ||= ::Deserializers::Xml::Contact.new(frame).call + def call + obj = domain + obj[:admin_domain_contacts_attributes] = admin_contacts + obj[:tech_domain_contacts_attributes] = tech_contacts + obj[:nameservers_attributes] = nameservers + obj[:dnskeys_attributes] = dns_keys + + obj end - def ident - @ident ||= ::Deserializers::Xml::Ident.new(frame).call + def domain + @domain ||= ::Deserializers::Xml::Domain.new(frame, registrar).call end - def legal_document - @legal_document ||= ::Deserializers::Xml::LegalDocument.new(frame).call + def nameservers + @nameservers ||= ::Deserializers::Xml::Nameservers.new(frame).call + end + + def admin_contacts + frame.css('contact').map { |c| c.text if c['type'] == 'admin' } + end + + def tech_contacts + frame.css('contact').map { |c| c.text if c['type'] == 'tech' } + end + + def dns_keys + @dns_keys ||= ::Deserializers::Xml::DnssecKeys.new(frame).key_data end end end