Merge branch 'master' into monthly-invoices-fix

This commit is contained in:
Sergei Tsõganov 2022-12-01 14:36:20 +02:00 committed by GitHub
commit 3ae24f2f68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
282 changed files with 2625 additions and 6669 deletions

View file

@ -31,12 +31,6 @@ class Ability
end
def epp # Registrar/api_user dynamic role
if @user.registrar.api_ip_white?(@ip)
can :manage, Depp::Contact
can :manage, :xml_console
can :manage, Depp::Domain
end
can :manage, Account
# Poll
@ -122,7 +116,6 @@ class Ability
customer_service
can :manage, :registrant_domains
can :manage, :registrant_whois
can :manage, Depp::Domain
can :manage, Domain
end

View file

@ -37,6 +37,24 @@ class Auction < ApplicationRecord
find_by(domain: domain_name.to_s, status: PENDING_STATUSES)
end
def self.domain_exists_in_blocked_disputed_and_registered?(domain_name)
Domain.exists?(name: domain_name) ||
BlockedDomain.exists?(name: domain_name) ||
Dispute.exists?(domain_name: domain_name) ||
exception_for_registred_or_unbided_existed_auctions(domain_name)
end
def self.exception_for_registred_or_unbided_existed_auctions(domain_name)
return false unless Auction.exists?(domain: domain_name)
auctions = Auction.where(domain: domain_name).order(:created_at)
last_record = auctions.last
return false if last_record.domain_registered? || last_record.no_bids?
true
end
def start
self.status = self.class.statuses[:started]
save!

View file

@ -59,8 +59,9 @@ module Invoice::BookKeeping
duration.times do |dur|
single_item_dup = single_item.dup
single_item_dup['start_date'] = (issue_date + dur.year).end_of_month.strftime('%Y-%m-%d')
single_item_dup['end_date'] = (issue_date + (dur + 1).year).end_of_month.strftime('%Y-%m-%d')
date = (issue_date + dur.year).end_of_month.strftime('%Y-%m-%d')
single_item_dup['start_date'] = date
single_item_dup['end_date'] = date
single_item_dup['price'] = (item['price'].to_f / duration).round(2)
lines << single_item_dup
end

View file

@ -38,15 +38,26 @@ module Registrar::BookKeeping
lines.as_json
end
def find_or_init_monthly_invoice(month:)
def find_or_init_monthly_invoice(month:, overwrite:)
invoice = invoices.find_by(monthly_invoice: true, issue_date: month.end_of_month.to_date,
cancelled_at: nil)
return invoice if invoice
return invoice if invoice && !overwrite
summary = monthly_summary(month: month)
return unless summary
init_monthly_invoice(summary)
new_invoice = init_monthly_invoice(summary)
return overwrite_invoice(invoice, new_invoice) if invoice && overwrite
new_invoice
end
def overwrite_invoice(original_invoice, new_invoice)
params_to_scrub = %i[created_at updated_at id number sent_at
e_invoice_sent_at in_directo cancelled_at payment_link]
attrs = new_invoice.attributes.with_indifferent_access.except(*params_to_scrub)
original_invoice.update(attrs)
original_invoice
end
def title_for_summary(date)

View file

@ -32,27 +32,40 @@ module Zone::WhoisQueryable
# Take note - since this concern only used to zone whois queries, dnssec keys are set to
# empty array
def domain_vars
{ disclaimer: Setting.registry_whois_disclaimer, name: origin,
{
disclaimer: Setting.registry_whois_disclaimer, name: origin,
registered: created_at.try(:to_s, :iso8601), status: ['ok (paid and in zone)'],
changed: updated_at.try(:to_s, :iso8601), email: Setting.registry_email,
admin_contacts: [contact_vars], tech_contacts: [contact_vars],
nameservers: nameserver_vars, dnssec_keys: [], dnssec_changed: nil }
nameservers: nameserver_vars, dnssec_keys: [],
dnssec_changed: nil
}
end
def registrar_vars
{ registrar: Setting.registry_juridical_name, registrar_website: Setting.registry_url,
registrar_phone: Setting.registry_phone }
{
registrar: Setting.registry_juridical_name,
registrar_website: Setting.registry_url,
registrar_phone: Setting.registry_phone,
}
end
def registrant_vars
{ registrant: Setting.registry_juridical_name, registrant_reg_no: Setting.registry_reg_no,
registrant_ident_country_code: Setting.registry_country_code, registrant_kind: 'org',
registrant_disclosed_attributes: %w[name email] }
{
registrant: Setting.registry_juridical_name,
registrant_reg_no: Setting.registry_reg_no,
registrant_ident_country_code: Setting.registry_country_code,
registrant_kind: 'org',
registrant_disclosed_attributes: %w[name email phone],
}
end
def contact_vars
{ name: Setting.registry_invoice_contact, email: Setting.registry_email,
disclosed_attributes: %w[name email] }
{
name: Setting.registry_invoice_contact,
email: Setting.registry_email,
disclosed_attributes: %w[name email],
}
end
def nameserver_vars

View file

@ -64,6 +64,8 @@ class Contact < ApplicationRecord
validate :validate_html
validate :validate_country_code, if: -> { self.class.address_processing? }
validates :registrant_publishable, inclusion: { in: [true, false] }, if: -> { registrant? }
# validates :registrant_publishable, inclusion: { in: [false] }, unless: -> { registrant? }
after_initialize do
self.status_notes = {} if status_notes.nil?
@ -145,6 +147,19 @@ class Contact < ApplicationRecord
# "clientDeleteProhibited" or "serverDeleteProhibited" status.
PENDING_DELETE = 'pendingDelete'.freeze
DISCLOSE_ATTRIBUTES = %w[
name
email
phone
registrant_publishable
address
fax
].freeze
OPEN_LEGAL_ATTRIBUTES = %w[
phone
].freeze
STATUSES = [
CLIENT_DELETE_PROHIBITED, SERVER_DELETE_PROHIBITED,
CLIENT_TRANSFER_PROHIBITED,

View file

@ -15,15 +15,6 @@ class ContactRequest < ApplicationRecord
attr_readonly :secret,
:valid_to
def self.save_record(params)
contact_request = new(params)
contact_request.secret = create_random_secret
contact_request.valid_to = set_valid_to_24_hours_from_now
contact_request.status = STATUS_NEW
contact_request.save!
contact_request
end
def update_record(params)
self.status = params['status'] if params['status']
self.ip_address = params['ip'] if params['ip']
@ -31,11 +22,22 @@ class ContactRequest < ApplicationRecord
save!
end
def self.create_random_secret
SecureRandom.hex(64)
end
class << self
def save_record(params)
contact_request = new(params)
contact_request.secret = create_random_secret
contact_request.valid_to = set_valid_to_24_hours_from_now
contact_request.status = STATUS_NEW
contact_request.save!
contact_request
end
def self.set_valid_to_24_hours_from_now
(Time.zone.now + 24.hours)
def create_random_secret
SecureRandom.hex(64)
end
def set_valid_to_24_hours_from_now
(Time.zone.now + 24.hours)
end
end
end

View file

@ -1,318 +0,0 @@
module Depp
class Contact
include ActiveModel::Model
attr_accessor :id, :name, :email, :phone, :org_name,
:ident, :ident_type, :ident_country_code,
:street, :city, :zip, :state, :country_code,
:password, :legal_document, :statuses, :code,
:email_history
DISABLED = 'Disabled'
DISCLOSURE_TYPES = [DISABLED, '1', '0']
TYPES = %w( org priv birthday )
SELECTION_TYPES = [
['Business code', 'org'],
['Personal identification code', 'priv'],
['Birthday', 'birthday']
]
validates :phone, e164: true, phone: true
class << self
attr_reader :epp_xml, :user
def new_from_params(params)
new(
id: params[:code],
code: params[:code],
email: params[:email],
phone: params[:phone],
ident: params[:ident],
ident_type: params[:ident_type],
ident_country_code: params[:ident_country_code],
# postalInfo
name: params[:name],
org_name: params[:org_name],
# address
street: params[:street],
city: params[:city],
zip: params[:zip],
state: params[:state],
country_code: params[:country_code]
)
end
def find_by_id(id)
data = info_xml(id)
res = data.css('epp response resData infData')
ext = data.css('epp response extension')
new(
id: res.css('id').text,
code: res.css('id').text,
email: res.css('email').text,
phone: res.css('voice').text,
ident: ext.css('ident').text,
ident_type: ext.css('ident').first.try(:attributes).try(:[], 'type').try(:value),
ident_country_code: ext.css('ident').first.try(:attributes).try(:[], 'cc').try(:value),
# postalInfo
name: res.css('postalInfo name').text,
org_name: res.css('postalInfo org').text,
# address
street: res.css('postalInfo addr street').text,
city: res.css('postalInfo addr city').text,
zip: res.css('postalInfo addr pc').text,
state: res.css('postalInfo addr sp').text,
country_code: res.css('postalInfo addr cc').text,
# authInfo
password: res.css('authInfo pw').text,
# statuses
statuses: data.css('status').map { |s| [s['s'], s.text] }
)
end
def user=(user)
@user = user
@epp_xml = EppXml::Contact.new(cl_trid_prefix: user.tag, schema_prefix: 'contact-ee',
schema_version: '1.1')
end
def info_xml(id, password = nil)
xml = epp_xml.info(
id: { value: id },
authInfo: { pw: { value: password } }
)
user.request(xml)
end
def construct_check_hash_from_data(data)
res = data.css('epp response resData chkData cd')
@contacts = []
res.each do |_r|
id = res.css('id').try(:text)
reason = res.css('reason').present? ? res.css('reason').text : I18n.t(:available)
@contacts << { id: id, reason: reason }
end
@contacts
end
def contact_id_from_xml(data)
id = data.css('epp response resData creData id').text
id.blank? ? nil : id
end
def construct_create_disclosure_xml(cph, flag)
xml = { disclose: {} }
cph.each do |k, v|
xml[:disclose][k] = {}
xml[:disclose][k][:value] = v
end
xml[:disclose][:attrs] = {}
xml[:disclose][:attrs][:flag] = flag
xml.with_indifferent_access
end
# cpd = contact_params[:disclose]
def extract_disclosure_hash(cpd)
return {} unless cpd
cpd.delete_if { |k, v| v if v != '1' && k == 'flag' }
end
def extract_info_disclosure(data)
hash = {}
data.css('disclose').each do |d|
flag = d.attributes['flag'].value
next unless flag
hash[flag] = {}
d.children.each do |c|
hash[flag][c.name] = flag if %w( name email fax voice addr org_name ).include?(c.name)
end
end
hash
end
def type_string(type_code)
return '' if type_code.blank?
t = SELECTION_TYPES.select { |tp| tp.second == type_code }
t.try(:first).try(:first)
end
end
def save
return false unless valid?
hash = {
id: { value: code },
postalInfo: {
name: { value: name },
org: { value: org_name },
},
voice: { value: phone },
email: { value: email }
}
if ::Contact.address_processing?
hash[:postalInfo][:addr] = {
street: { value: street },
city: { value: city },
sp: { value: state },
pc: { value: zip },
cc: { value: country_code },
}
end
hash[:id] = nil if code.blank?
create_xml = Depp::Contact.epp_xml.create(hash, extension_xml(:create))
data = Depp::Contact.user.request(create_xml)
self.id = data.css('id').text
handle_errors(data)
end
# rubocop:disable Metrics/MethodLength
def update_attributes(params)
return false unless valid?
self.ident_country_code = params[:ident_country_code]
self.ident_type = params[:ident_type]
self.ident = params[:ident]
self.name = params[:name]
self.email = params[:email]
self.phone = params[:phone]
self.org_name = params[:org_name]
if ::Contact.address_processing?
self.street = params[:street]
self.city = params[:city]
self.zip = params[:zip]
self.state = params[:state]
self.country_code = params[:country_code]
end
attributes = {
id: { value: id },
chg: {
postalInfo: {
name: { value: name },
org: { value: org_name },
},
voice: { value: phone },
email: { value: email },
authInfo: {
pw: { value: password }
}
}
}
if ::Contact.address_processing?
attributes[:chg][:postalInfo][:addr] = {
street: { value: street },
city: { value: city },
sp: { value: state },
pc: { value: zip },
cc: { value: country_code }
}
end
update_xml = Depp::Contact.epp_xml.update(attributes, extension_xml(:update))
data = Depp::Contact.user.request(update_xml)
handle_errors(data)
end
# rubocop:enable Metrics/MethodLength
def delete
delete_xml = Contact.epp_xml.delete(
{
id: { value: id },
authInfo: { pw: { value: password } }
},
extension_xml(:delete)
)
data = Depp::Contact.user.request(delete_xml)
handle_errors(data)
end
def extension_xml(action)
xml = { _anonymus: [] }
case action
when :create
ident = ident_xml[:_anonymus].try(:first)
when :update
# detect if any ident has changed, nb! ident and self.ident is not always same
unless ident == self.ident && ident == ident_type && ident_country_code == self.ident_country_code
ident = ident_xml[:_anonymus].try(:first)
end
end
legal = legal_document_xml[:_anonymus].try(:first)
xml[:_anonymus] << ident if ident.present?
xml[:_anonymus] << legal if legal.present?
xml
end
def ident_xml
{
_anonymus: [
ident: { value: ident, attrs: { type: ident_type, cc: ident_country_code } }
]
}
end
def legal_document_xml
return {} if legal_document.blank?
type = legal_document.original_filename.split('.').last.downcase
{
_anonymus: [
legalDocument: { value: Base64.encode64(legal_document.read), attrs: { type: type } }
]
}
end
def check(id)
xml = epp_xml.check(id: { value: id })
current_user.request(xml)
end
def country_name
Country.new(country_code) || 'No access'
end
def org?
ident_type == 'org'
end
def priv?
ident_type == 'priv'
end
def persisted?
id.present?
end
def handle_errors(data)
data.css('result').each do |x|
success_codes = %(1000, 1300, 1301)
next if success_codes.include?(x['code'])
message = "#{x.css('msg').text} #{x.css('value').text}"
attr = message.split('[').last.strip.sub(']', '') if message.include?('[')
attr = :base if attr.nil?
attr = 'phone' if attr == 'voice'
attr = 'zip' if attr == 'pc'
errors.add(attr, message)
end
errors.blank?
end
end
end

View file

@ -1,26 +0,0 @@
module Depp
class Dnskey
FLAGS = [
['0 - not for DNSSEC validation', 0],
['256 - ZSK', 256],
['257 - KSK', 257]
]
ALGORITHMS = [
['3 - DSA/SHA-1', 3],
['5 - RSA/SHA-1', 5],
['6 - DSA-NSEC3-SHA1', 6],
['7 - RSASHA1-NSEC3-SHA1', 7],
['8 - RSA/SHA-256', 8],
['10 - RSA/SHA-512', 10],
['13 - ECDSA Curve P-256 with SHA-256', 13],
['14 - ECDSA Curve P-384 with SHA-384', 14],
['15 - Ed25519', 15],
['16 - Ed448', 16],
].freeze
PROTOCOLS = [3]
DS_DIGEST_TYPES = [1, 2]
end
end

View file

@ -1,351 +0,0 @@
module Depp
class Domain
include ActiveModel::Conversion
include RemoveHold
extend ActiveModel::Naming
attr_accessor :name, :current_user, :epp_xml
STATUSES = %w[
clientDeleteProhibited
clientHold
clientRenewProhibited
clientTransferProhibited
clientUpdateProhibited
].freeze
PERIODS = [
['3 months', '3m'],
['6 months', '6m'],
['9 months', '9m'],
['1 year', '1y'],
['2 years', '2y'],
['3 years', '3y'],
['4 years', '4y'],
['5 years', '5y'],
['6 years', '6y'],
['7 years', '7y'],
['8 years', '8y'],
['9 years', '9y'],
['10 years', '10y'],
].freeze
def initialize(args = {})
self.current_user = args[:current_user]
self.epp_xml = EppXml::Domain.new(
cl_trid_prefix: current_user.tag,
schema_prefix: 'domain-ee',
schema_version: '1.1'
)
end
def info(domain_name)
xml = epp_xml.info(name: { value: domain_name })
current_user.request(xml)
end
def check(domain_name)
xml = epp_xml.check(
_anonymus: [
name: { value: domain_name }
]
)
current_user.request(xml)
end
def create(domain_params)
dns_hash = {}
keys = Domain.create_dnskeys_hash(domain_params)
dns_hash[:_anonymus] = keys if keys.any?
period = domain_params[:period].to_i.to_s
period_unit = domain_params[:period][-1].to_s
xml = if domain_params[:nameservers_attributes]
.select { |_key, value| value['hostname'].present? }.any?
epp_xml.create({
name: { value: domain_params[:name] },
period: { value: period, attrs: { unit: period_unit } },
ns: Domain.create_nameservers_hash(domain_params),
registrant: { value: domain_params[:registrant] },
_anonymus: Domain.create_contacts_hash(domain_params)
}, dns_hash, Domain.construct_custom_params_hash(domain_params))
else
epp_xml.create({
name: { value: domain_params[:name] },
period: { value: period, attrs: { unit: period_unit } },
registrant: { value: domain_params[:registrant] },
_anonymus: Domain.create_contacts_hash(domain_params)
}, dns_hash, Domain.construct_custom_params_hash(domain_params))
end
current_user.request(xml)
end
def update(domain_params)
data = current_user.request(epp_xml.info(name: { value: domain_params[:name] }))
old_domain_params = Depp::Domain.construct_params_from_server_data(data)
xml = epp_xml.update(
Depp::Domain.construct_edit_hash(domain_params, old_domain_params),
Depp::Domain.construct_ext_edit_hash(domain_params, old_domain_params),
Depp::Domain.construct_custom_params_hash(domain_params)
)
current_user.request(xml)
end
def delete(domain_params)
xml = epp_xml.delete({
name: { value: domain_params[:name] },
},
Depp::Domain.construct_custom_params_hash(domain_params),
(domain_params[:verified].present? && 'yes'))
current_user.request(xml)
end
def renew(params)
period = params[:period].to_i.to_s
period_unit = params[:period][-1].to_s
current_user.request(epp_xml.renew(name: { value: params[:domain_name] },
curExpDate: { value: params[:cur_exp_date] },
period: { value: period, attrs: { unit: period_unit } }))
end
def transfer(params)
op = params[:request] ? 'request' : nil
op = params[:query] ? 'query' : op
op = params[:approve] ? 'approve' : op
op = params[:reject] ? 'reject' : op
current_user.request(epp_xml.transfer({
name: { value: params[:domain_name] },
authInfo: { pw: { value: params[:transfer_code] } }
}, op, Domain.construct_custom_params_hash(params)))
end
def confirm_transfer(domain_params)
data = current_user.request(epp_xml.info(name: { value: domain_params[:name] }))
pw = data.css('pw').text
xml = epp_xml.transfer({
name: { value: domain_params[:name] },
authInfo: { pw: { value: pw } }
}, 'approve')
current_user.request(xml)
end
class << self
def default_period
'1y'
end
def default_params
ret = {}
ret[:contacts_attributes] ||= {}
ENV['default_admin_contacts_count'].to_i.times do |i|
ret[:contacts_attributes][i] = { code: '', type: 'admin' }
end
ret[:nameservers_attributes] ||= {}
ENV['default_nameservers_count'].to_i.times do |i|
ret[:nameservers_attributes][i] = {}
end
ret[:dnskeys_attributes] ||= { 0 => {} }
ret[:statuses_attributes] ||= { 0 => {} }
ret.with_indifferent_access
end
def construct_params_from_server_data(data)
ret = default_params
ret[:name] = data.css('name').text
ret[:registrant] = data.css('registrant').text
data.css('contact').each_with_index do |x, i|
ret[:contacts_attributes][i] = { code: x.text, type: x['type'] }
end
data.css('hostAttr').each_with_index do |x, i|
ret[:nameservers_attributes][i] = {
hostname: x.css('hostName').text,
ipv4: Array(x.css('hostAddr[ip="v4"]')).map(&:text).join(','),
ipv6: Array(x.css('hostAddr[ip="v6"]')).map(&:text).join(',')
}
end
data.css('keyData').each_with_index do |x, i|
ret[:dnskeys_attributes][i] = {
flags: x.css('flags').text,
protocol: x.css('protocol').text,
alg: x.css('alg').text,
public_key: x.css('pubKey').text,
ds_key_tag: x.css('keyTag').first.try(:text),
ds_alg: x.css('alg').first.try(:text),
ds_digest_type: x.css('digestType').first.try(:text),
ds_digest: x.css('digest').first.try(:text)
}
end
data.css('status').each_with_index do |x, i|
next unless STATUSES.include?(x['s'])
ret[:statuses_attributes][i] = {
code: x['s'],
description: x.text
}
end
ret
end
def construct_custom_params_hash(domain_params)
custom_params = { _anonymus: [] }
if domain_params[:legal_document].present?
type = domain_params[:legal_document].original_filename.split('.').last.downcase
custom_params[:_anonymus] << {
legalDocument: { value: Base64.encode64(domain_params[:legal_document].read), attrs: { type: type } }
}
end
if domain_params[:reserved_pw].present?
custom_params[:_anonymus] << { reserved: { pw: { value: domain_params[:reserved_pw] } } }
end
custom_params
end
def construct_edit_hash(domain_params, old_domain_params)
contacts = array_difference(create_contacts_hash(domain_params), create_contacts_hash(old_domain_params))
add_anon = contacts
contacts = array_difference(create_contacts_hash(old_domain_params), create_contacts_hash(domain_params))
rem_anon = contacts
add_arr = []
add_ns = create_nameservers_hash(domain_params) - create_nameservers_hash(old_domain_params)
add_arr << { ns: add_ns } if add_ns.any?
add_arr << { _anonymus: add_anon } if add_anon.any?
rem_arr = []
rem_ns = create_nameservers_hash(old_domain_params) - create_nameservers_hash(domain_params)
rem_arr << { ns: rem_ns } if rem_ns.any?
rem_arr << { _anonymus: rem_anon } if rem_anon.any?
if domain_params[:registrant] != old_domain_params[:registrant]
chg = [{ registrant: { value: domain_params[:registrant] } }] unless domain_params[:verified].present?
if domain_params[:verified]
chg = [{ registrant: { value: domain_params[:registrant], attrs: { verified: 'yes' } } }]
end
end
add_arr = nil if add_arr.none?
rem_arr = nil if rem_arr.none?
{
name: { value: domain_params[:name] },
add: add_arr,
rem: rem_arr,
chg: chg
}
end
def construct_ext_edit_hash(domain_params, old_domain_params)
rem_keys = create_dnskeys_hash(old_domain_params) - create_dnskeys_hash(domain_params)
add_keys = create_dnskeys_hash(domain_params) - create_dnskeys_hash(old_domain_params)
hash = {}
hash[:rem] = rem_keys if rem_keys.any?
hash[:add] = add_keys if add_keys.any?
hash
end
def create_nameservers_hash(domain_params)
ret = []
domain_params[:nameservers_attributes].each do |_k, v|
next if v['hostname'].blank?
host_attr = []
host_attr << { hostName: { value: v['hostname'] } }
if v['ipv4'].present?
v['ipv4'].to_s.split(',').each do |ip|
host_attr << { hostAddr: { value: ip, attrs: { ip: 'v4' } } }
end
end
if v['ipv6'].present?
v['ipv6'].to_s.split(',').each do |ip|
host_attr << { hostAddr: { value: ip, attrs: { ip: 'v6' } } }
end
end
ret << { hostAttr: host_attr }
end
ret
end
def create_contacts_hash(domain_params)
ret = []
domain_params[:contacts_attributes].each do |_k, v|
next if v['code'].blank?
ret << {
contact: { value: v['code'], attrs: { type: v['type'] } }
}
end
ret
end
def create_dnskeys_hash(domain_params)
ret = []
domain_params[:dnskeys_attributes].each do |_k, v|
if v['ds_key_tag'].blank?
kd = create_key_data_hash(v)
if kd
ret << {
keyData: kd
}
end
else
ret << {
dsData: [
keyTag: { value: v['ds_key_tag'] },
alg: { value: v['ds_alg'] },
digestType: { value: v['ds_digest_type'] },
digest: { value: v['ds_digest'] },
keyData: create_key_data_hash(v)
]
}
end
end
ret
end
def create_key_data_hash(key_data_params)
return nil if key_data_params['public_key'].blank?
{
flags: { value: key_data_params['flags'] },
protocol: { value: key_data_params['protocol'] },
alg: { value: key_data_params['alg'] },
pubKey: { value: key_data_params['public_key'] }
}
end
def array_difference(x, y)
ret = x.dup
y.each do |element|
index = ret.index(element)
ret.delete_at(index) if index
end
ret
end
end
end
end

View file

@ -1,66 +0,0 @@
module Depp
class User
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :tag, :password, :pki
validates :tag, :password, presence: true
validate :validate_existance_in_server
def initialize(args = {})
args.each { |k, v| send("#{k}=", v) }
end
def server
client_cert = File.read(ENV['cert_path'])
client_key = File.read(ENV['key_path'])
port = ENV['epp_port'] || '700'
@server_cache ||= Epp::Server.new({
server: ENV['epp_hostname'],
tag: tag,
password: password,
port: port,
cert: OpenSSL::X509::Certificate.new(client_cert),
key: OpenSSL::PKey::RSA.new(client_key)
})
end
def request(xml)
Nokogiri::XML(server.request(xml)).remove_namespaces!
rescue EppErrorResponse => e
Nokogiri::XML(e.response_xml.to_s).remove_namespaces!
end
private
def validate_existance_in_server
return if errors.any?
res = server.open_connection
unless Nokogiri::XML(res).css('greeting')
errors.add(:base, :failed_to_open_connection_to_epp_server)
server.close_connection # just in case
return
end
ex = EppXml::Session.new(cl_trid_prefix: tag)
xml = ex.login(clID: { value: tag }, pw: { value: password })
res = server.send_request(xml)
if Nokogiri::XML(res).css('result').first['code'] != '1000'
errors.add(:base, Nokogiri::XML(res).css('result').text)
end
server.close_connection
rescue OpenSSL::SSL::SSLError => e
Rails.logger.error "INVALID CERT: #{e}"
Rails.logger.error "INVALID CERT DEBUG INFO: epp_hostname: #{ENV['epp_hostname']}," \
"port: #{ENV['epp_port']}, cert_path: #{ENV['cert_path']}, key_path: #{ENV['key_path']}"
errors.add(:base, :invalid_cert)
end
end
end

View file

@ -25,10 +25,10 @@ class Dnskey < ApplicationRecord
}
# IANA numbers, single authority list
ALGORITHMS = Depp::Dnskey::ALGORITHMS.map {|pair| pair[1].to_s}.freeze
PROTOCOLS = %w(3)
FLAGS = %w(0 256 257) # 256 = ZSK, 257 = KSK
DS_DIGEST_TYPE = [1,2]
ALGORITHMS = %w[3 5 6 7 8 10 13 14 15 16].freeze
PROTOCOLS = %w[3].freeze
FLAGS = %w[0 256 257].freeze # 256 = ZSK, 257 = KSK
DS_DIGEST_TYPE = [1, 2].freeze
RESOLVERS = ENV['dnssec_resolver_ips'].to_s.strip.split(', ').freeze
self.ignored_columns = %w[legacy_domain_id]

View file

@ -13,6 +13,22 @@ class Domain < ApplicationRecord
include Domain::Disputable
include Domain::BulkUpdatable
PERIODS = [
['3 months', '3m'],
['6 months', '6m'],
['9 months', '9m'],
['1 year', '1y'],
['2 years', '2y'],
['3 years', '3y'],
['4 years', '4y'],
['5 years', '5y'],
['6 years', '6y'],
['7 years', '7y'],
['8 years', '8y'],
['9 years', '9y'],
['10 years', '10y'],
].freeze
attr_accessor :roles,
:legal_document_id,
:is_admin,
@ -49,7 +65,6 @@ class Domain < ApplicationRecord
statuses.include? DomainStatus::SERVER_REGISTRANT_CHANGE_PROHIBITED
end
# NB! contacts, admin_contacts, tech_contacts are empty for a new record
has_many :domain_contacts, dependent: :destroy
has_many :contacts, through: :domain_contacts, source: :contact

View file

@ -105,6 +105,9 @@ class Epp::Domain < Domain
max: Setting.ns_max_count
}
],
'2502' => [ # Rate limit exceeded
%i[base session_limit_exceeded],
],
]
}
end

View file

@ -62,7 +62,7 @@ module Epp
2308 => 'Data management policy violation',
2400 => 'Command failed',
2501 => 'Authentication error; server closing connection',
2502 => 'Session limit exceeded; server closing connection',
2502 => Shunter.default_error_message,
}.freeze
private_constant :DEFAULT_DESCRIPTIONS

View file

@ -1,4 +1,4 @@
class LegalDocument < ApplicationRecord
class LegalDocument < ApplicationRecord # rubocop:disable Metrics/ClassLength
include EppErrors
MIN_BODY_SIZE = (1.37 * 3.kilobytes).ceil
MAX_BODY_SIZE = 8.megabytes
@ -14,7 +14,7 @@ class LegalDocument < ApplicationRecord
belongs_to :documentable, polymorphic: true
validate :val_body_length, if: ->(file) { file.path.blank? }
validate :val_body_length, if: ->(file) { file.path.blank? && (Rails.env.production? || Rails.env.test?) }
before_create :add_creator
before_save :save_to_filesystem, if: :body
@ -24,7 +24,7 @@ class LegalDocument < ApplicationRecord
'2308' => [
%i[body length_more_than],
%i[body length_less_than],
]
],
}
end
@ -41,12 +41,13 @@ class LegalDocument < ApplicationRecord
digest = Digest::SHA1.new.update(binary).to_s
loop do
rand = SecureRandom.random_number.to_s.last(4)
next if rand.to_i == 0 || rand.length < 4
dir = "#{ENV['legal_documents_dir']}/#{Time.zone.now.strftime('%Y/%m/%d')}"
FileUtils.mkdir_p(dir, mode: 0775)
self.path = "#{dir}/#{Time.zone.now.to_formatted_s(:number)}_#{rand}.#{document_type}"
break unless File.file?(path)
rand = SecureRandom.random_number.to_s.last(4)
next if rand.to_i.zero? || rand.length < 4
dir = "#{ENV['legal_documents_dir']}/#{Time.zone.now.strftime('%Y/%m/%d')}"
FileUtils.mkdir_p(dir, mode: 0775)
self.path = "#{dir}/#{Time.zone.now.to_formatted_s(:number)}_#{rand}.#{document_type}"
break unless File.file?(path)
end
File.open(path, 'wb') { |f| f.write(binary) } unless Rails.env.test?
@ -69,50 +70,57 @@ class LegalDocument < ApplicationRecord
start = Time.zone.now.to_f
Rails.logger.info '-----> Removing legal documents duplicates'
count = 0
modified = Array.new
modified = []
LegalDocument.where(documentable_type: "Domain").where.not(checksum: [nil, ""]).find_each do |orig_legal|
LegalDocument.where(documentable_type: 'Domain')
.where.not(checksum: [nil, ''])
.find_each do |orig_legal|
next if modified.include?(orig_legal.checksum)
next if !File.exist?(orig_legal.path)
next unless File.exist?(orig_legal.path)
modified.push(orig_legal.checksum)
LegalDocument.where(documentable_type: "Domain", documentable_id: orig_legal.documentable_id).
where(checksum: orig_legal.checksum).
where.not(id: orig_legal.id).where.not(path: orig_legal.path).each do |new_legal|
unless modified.include?(orig_legal.id)
File.delete(new_legal.path) if File.exist?(new_legal.path)
new_legal.update(path: orig_legal.path)
count += 1
Rails.logger.info "File #{new_legal.path} has been removed by Domain "\
"#{new_legal.documentable_id}. Document id: #{new_legal.id}"
end
LegalDocument.where(documentable_type: 'Domain', documentable_id: orig_legal.documentable_id)
.where(checksum: orig_legal.checksum)
.where.not(id: orig_legal.id)
.where.not(path: orig_legal.path).each do |new_legal|
next if modified.include?(orig_legal.id)
File.delete(new_legal.path) if File.exist?(new_legal.path)
new_legal.update(path: orig_legal.path)
count += 1
Rails.logger.info "File #{new_legal.path} has been removed by Domain "\
"#{new_legal.documentable_id}. Document id: #{new_legal.id}"
end
contact_ids = Version::DomainVersion.where(item_id: orig_legal.documentable_id).distinct.
pluck("object->>'registrant_id'", "object_changes->>'registrant_id'",
"children->>'tech_contacts'", "children->>'admin_contacts'").flatten.uniq
contact_ids = contact_ids.map{|id|
contact_ids = Version::DomainVersion.where(item_id: orig_legal.documentable_id).distinct
.pluck("object->>'registrant_id'",
"object_changes->>'registrant_id'",
"children->>'tech_contacts'",
"children->>'admin_contacts'")
.flatten.uniq
contact_ids = contact_ids.map do |id|
case id
when Hash
id["id"]
when String
JSON.parse(id) rescue id.to_i
else
id
end
}.flatten.compact.uniq
LegalDocument.where(documentable_type: "Contact", documentable_id: contact_ids).
where(checksum: orig_legal.checksum).where.not(path: orig_legal.path).each do |new_legal|
unless modified.include?(orig_legal.id)
File.delete(new_legal.path) if File.exist?(new_legal.path)
new_legal.update(path: orig_legal.path)
count += 1
Rails.logger.info "File #{new_legal.path} has been removed by Contact "\
"#{new_legal.documentable_id}. Document id: #{new_legal.id}"
when Hash
id['id']
when String
JSON.parse(id) rescue id.to_i
else
id
end
end.flatten.compact.uniq
LegalDocument.where(documentable_type: 'Contact', documentable_id: contact_ids)
.where(checksum: orig_legal.checksum)
.where.not(path: orig_legal.path).each do |new_legal|
next if modified.include?(orig_legal.id)
File.delete(new_legal.path) if File.exist?(new_legal.path)
new_legal.update(path: orig_legal.path)
count += 1
Rails.logger.info "File #{new_legal.path} has been removed by Contact "\
"#{new_legal.documentable_id}. Document id: #{new_legal.id}"
end
end
Rails.logger.info "-----> Duplicates fixed for #{count} rows in #{(Time.zone.now.to_f - start).round(2)} seconds"
end
end

View file

@ -3,4 +3,8 @@ class Registrant < Contact
def epp_code_map
{}
end
def publishable?
registrant_publishable
end
end

View file

@ -23,7 +23,7 @@ class ValidationEvent < ApplicationRecord
smtp: 1,
}.freeze
store_accessor :event_data, :errors, :check_level, :email
store_accessor :event_data, :check_level, :email
belongs_to :validation_eventable, polymorphic: true

View file

@ -51,7 +51,9 @@ class WhoisRecord < ApplicationRecord
end
h[:email] = registrant.email
h[:registrant_changed] = registrant.updated_at.try(:to_s, :iso8601)
h[:phone] = registrant.phone
h[:registrant_publishable] = registrant.publishable?
h[:registrant_changed] = registrant.updated_at.try(:to_s, :iso8601)
h[:registrant_disclosed_attributes] = registrant.disclosed_attributes
h[:admin_contacts] = []
@ -78,7 +80,6 @@ class WhoisRecord < ApplicationRecord
h[:dnssec_keys] = domain.dnskeys.map { |key| "#{key.flags} #{key.protocol} #{key.alg} #{key.public_key}" }
h[:dnssec_changed] = domain.dnskeys.pluck(:updated_at).max.try(:to_s, :iso8601) rescue nil
h
end
@ -112,8 +113,10 @@ class WhoisRecord < ApplicationRecord
{
name: contact.name,
email: contact.email,
phone: contact.phone,
changed: contact.updated_at.try(:to_s, :iso8601),
disclosed_attributes: contact.disclosed_attributes,
contact_publishable: contact.registrant_publishable?,
}
end
end