Merge remote-tracking branch 'origin/master' into repp-domains

This commit is contained in:
Karl Erik Õunapuu 2021-01-28 16:18:45 +02:00
commit 43e5b74668
No known key found for this signature in database
GPG key ID: C9DD647298A34764
29 changed files with 357 additions and 49 deletions

View file

@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-18.04]
ruby: [2.6, 2.7 ]
ruby: [ 2.7 ]
runs-on: ${{ matrix.os }}
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
steps:
@ -100,11 +100,6 @@ jobs:
- name: Give test coverage reporter executable permissions
run: chmod +x cc-test-reporter
- uses: actions/download-artifact@v1
with:
name: coverage-2.6
path: coverage
- uses: actions/download-artifact@v1
with:
name: coverage-2.7

View file

@ -1 +1 @@
2.6.5
2.7.2

View file

@ -1,3 +1,24 @@
28.01.2021
* Fixed transfer with shared admin and tech contacts [#1808](https://github.com/internetee/registry/issues/1808)
* Improved DNSSEC key validation for illegal characters [#1790](https://github.com/internetee/registry/issues/1790)
* Fix for whois record creation issue on releasing domain to auction [#1139](https://github.com/internetee/registry/issues/1139)
* Improved registrar account activity tests [#1824](https://github.com/internetee/registry/pull/1824)
27.01.2021
* Figaro update to 1.2.0 [#1823](https://github.com/internetee/registry/pull/1823)
26.01.2021
* Ruby update to 2.7 [#1791](https://github.com/internetee/registry/issues/1791)
21.01.2021
* Registrant API: optimised contact linking [#1807](https://github.com/internetee/registry/pull/1807)
20.01.2021
* Fixed legaldoc assignment issue on registrant confirmation [#1806](https://github.com/internetee/registry/pull/1806)
14.01.2021
* Fixed IDN and punycode support for REPP domain transfer_info request [#1801](https://github.com/internetee/registry/issues/1801)
06.01.2021
* IMproved tests whois update for bulk nameserver change [#1739](https://github.com/internetee/registry/issues/1739)
* Bulk ForceDelete funcionality in admin [#1177](https://github.com/internetee/registry/issues/1177)

View file

@ -1,4 +1,4 @@
FROM internetee/ruby:2.6-buster
FROM internetee/ruby:2.7-buster
RUN mkdir -p /opt/webapps/app/tmp/pids
WORKDIR /opt/webapps/app

View file

@ -10,7 +10,7 @@ gem 'rest-client'
gem 'uglifier'
# load env
gem 'figaro', '1.1.1'
gem 'figaro', '~> 1.2'
# model related
gem 'activerecord-import'

View file

@ -204,8 +204,8 @@ GEM
erubis (2.7.0)
execjs (2.7.0)
ffi (1.13.1)
figaro (1.1.1)
thor (~> 0.14)
figaro (1.2.0)
thor (>= 0.14.0, < 2)
globalid (0.4.2)
activesupport (>= 4.2.0)
gyoku (1.3.1)
@ -506,7 +506,7 @@ DEPENDENCIES
e_invoice!
epp!
epp-xml (= 1.1.0)!
figaro (= 1.1.1)
figaro (~> 1.2)
haml (~> 5.0)
isikukood
iso8601 (= 0.12.1)

View file

@ -24,7 +24,7 @@ module Api
end
def show
contact = current_user_contacts.find_by(uuid: params[:uuid])
contact = representable_contact(params[:uuid])
links = params[:links] == 'true'
if contact
@ -91,6 +91,22 @@ module Api
private
def representable_contact(uuid)
country = current_registrant_user.country.alpha2
contact = Contact.find_by(uuid: uuid, ident: current_registrant_user.ident,
ident_type: 'priv', ident_country_code: country)
return contact if contact
Contact.find_by(uuid: uuid, ident_type: 'org', ident: company_codes,
ident_country_code: country)
rescue CompanyRegister::NotAvailableError
nil
end
def company_codes
current_registrant_user.companies.collect(&:registration_number)
end
def current_user_contacts
current_registrant_user.contacts(representable: false)
rescue CompanyRegister::NotAvailableError

View file

@ -1,7 +1,7 @@
module Repp
module V1
class BaseController < ActionController::API # rubocop:disable Metrics/ClassLength
rescue_from ActiveRecord::RecordNotFound, with: :not_found_error
around_action :log_request
before_action :authenticate_user
before_action :validate_webclient_ca
before_action :check_ip_restriction
@ -9,22 +9,31 @@ module Repp
before_action :set_paper_trail_whodunnit
rescue_from ActionController::ParameterMissing, Apipie::ParamInvalid,
Apipie::ParamMissing do |exception|
render json: { code: 2003, message: exception }, status: :bad_request
private
def log_request
yield
rescue ActiveRecord::RecordNotFound
@response = { code: 2303, message: 'Object does not exist' }
render(json: @response, status: :not_found)
rescue ActionController::ParameterMissing, Apipie::ParamInvalid, Apipie::ParamMissing => e
@response = { code: 2003, message: e }
render(json: @response, status: :bad_request)
ensure
create_repp_log
end
after_action do
# rubocop:disable Metrics/AbcSize
def create_repp_log
ApiLog::ReppLog.create(
request_path: request.path, request_method: request.request_method,
request_params: request.params.except('route_info').to_json, uuid: request.try(:uuid),
response: @response.to_json, response_code: status, ip: request.ip,
response: @response.to_json, response_code: response.status, ip: request.ip,
api_user_name: current_user.try(:username),
api_user_registrar: current_user.try(:registrar).try(:to_s)
)
end
private
# rubocop:enable Metrics/AbcSize
def set_domain
registrar = current_user.registrar
@ -131,11 +140,6 @@ module Repp
render(json: @response, status: :unauthorized)
end
def not_found_error
@response = { code: 2303, message: 'Object does not exist' }
render(json: @response, status: :not_found)
end
end
end
end

View file

@ -3,6 +3,7 @@ module Repp
module V1
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 destroy]
before_action :set_domain, only: %i[show update]
@ -182,11 +183,7 @@ module Repp
def set_authorized_domain
@epp_errors ||= []
h = {}
h[transfer_info_params[:id].match?(/\A[0-9]+\z/) ? :id : :name] = transfer_info_params[:id]
@domain = Epp::Domain.find_by!(h)
validate_registrar_authorization
@domain = domain_from_url_hash
end
def validate_registrar_authorization
@ -197,6 +194,13 @@ module Repp
handle_errors
end
def domain_from_url_hash
entry = transfer_info_params[:id]
return Domain.find(entry) if entry.match?(/\A[0-9]+\z/)
Domain.find_by!('name = ? OR name_puny = ?', entry, entry)
end
def limit
index_params[:limit] || 200
end

6
app/lib/to_stdout.rb Normal file
View file

@ -0,0 +1,6 @@
class ToStdout
def self.msg(message)
time = Time.zone.now.utc
STDOUT << "#{time} - #{message}\n" unless Rails.env.test?
end
end

View file

@ -68,7 +68,25 @@ module Actions
domain.registrar = current_registrar
assign_domain_period
assign_domain_auth_codes
domain.dnskeys_attributes = params[:dnskeys_attributes] if params[:dnskeys_attributes]
assign_dnskeys
end
def assign_dnskeys
return unless params[:dnskeys_attributes]&.any?
params[:dnskeys_attributes].each { |dk| verify_public_key_integrity(dk) }
params.dnskeys_attributes = params[:dnskeys_attributes]
end
def verify_public_key_integrity(dnssec)
return if dnssec[:public_key].blank?
value = dnssec[:public_key]
if !value.is_a?(String) || Base64.strict_encode64(Base64.strict_decode64(value)) != value
domain.add_epp_error(2005, nil, nil, %i[dnskeys invalid])
end
rescue ArgumentError
domain.add_epp_error(2005, nil, nil, %i[dnskeys invalid])
end
def assign_domain_auth_codes

View file

@ -92,18 +92,20 @@ module Actions
end
def validate_dnskey_integrity(key)
if key[:pubKey] && !Setting.key_data_allowed
if key[:public_key] && !Setting.key_data_allowed
domain.add_epp_error('2306', nil, nil, %i[dnskeys key_data_not_allowed])
elsif key[:digest] && !Setting.ds_data_allowed
elsif key[:ds_digest] && !Setting.ds_data_allowed
domain.add_epp_error('2306', nil, nil, %i[dnskeys ds_data_not_allowed])
end
verify_public_key_integrity(key)
@dnskeys << key.except(:action)
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
domain.add_epp_error(2303, nil, nil, %i[dnskeys not_found]) unless dnkey
@dnskeys << { id: dnkey.id, _destroy: 1 } if dnkey
end
@ -240,5 +242,16 @@ module Actions
false
end
def verify_public_key_integrity(dnssec)
return if dnssec[:public_key].blank?
value = dnssec[:public_key]
if !value.is_a?(String) || Base64.strict_encode64(Base64.strict_decode64(value)) != value
domain.add_epp_error('2005', nil, nil, %i[dnskeys invalid])
end
rescue ArgumentError
domain.add_epp_error('2005', nil, nil, %i[dnskeys invalid])
end
end
end

View file

@ -39,13 +39,15 @@ module Concerns
def release
if release_to_auction
transaction do
domain_name.sell_at_auction if domain_name.auctionable?
destroy!
registrar.notifications.create!(text: "#{I18n.t(:domain_deleted)}: #{name}",
attached_obj_id: id,
attached_obj_type: self.class)
end
ToStdout.msg 'Destroying domain'
destroy!
ToStdout.msg "Checking if domain_name is auctionable: #{domain_name.auctionable?}"
domain_name.sell_at_auction if domain_name.auctionable?
ToStdout.msg 'Sending registrar notification'
registrar.notifications.create!(text: "#{I18n.t(:domain_deleted)}: #{name}",
attached_obj_id: id,
attached_obj_type: self.class)
else
discard
end

View file

@ -59,7 +59,7 @@ module Concerns::Domain::Transferable
copied_ids = []
domain_contacts.each do |dc|
contact = Contact.find(dc.contact_id)
next if copied_ids.include?(contact.id) || contact.registrar == new_registrar
next if copied_ids.include?(uniq_contact_hash(dc)) || contact.registrar == new_registrar
if registrant_id_was == contact.id # registrant was copied previously, do not copy it again
oc = OpenStruct.new(id: registrant_id)
@ -72,7 +72,11 @@ module Concerns::Domain::Transferable
else
dc.update(contact_id: oc.id)
end
copied_ids << contact.id
copied_ids << uniq_contact_hash(dc)
end
end
def uniq_contact_hash(contact)
Digest::SHA1.hexdigest(contact.contact_id.to_s + contact.type)
end
end

View file

@ -360,9 +360,11 @@ class Contact < ApplicationRecord
@desc
end
# Limits returned objects to 11
def related_domains
a = related_domain_descriptions
a.keys.map { |d| { name: d, id: a[d][:id], roles: a[d][:roles] } }
ids = DomainContact.select(:domain_id).where(contact_id: id).limit(11).map(&:domain_id).uniq
res = Domain.where(id: ids).or(Domain.where(registrant_id: id)).select(:name, :uuid).limit(11)
res.pluck(:name, :uuid).map { |name, id| { name: name, id: id } }
end
def status_notes_array=(notes)

View file

@ -36,6 +36,7 @@ module DNS
auction = Auction.new
auction.domain = name
auction.start
ToStdout.msg "Created the auction: #{auction.inspect}"
update_whois_from_auction(auction)
end
@ -100,7 +101,8 @@ module DNS
whois_record = Whois::Record.find_or_create_by!(name: name) do |record|
record.json = {}
end
ToStdout.msg "Starting to update WHOIS record #{whois_record.inspect}\n\n"\
"from auction #{auction.inspect}"
whois_record.update_from_auction(auction)
end
end

View file

@ -78,7 +78,7 @@ class Domain < ApplicationRecord
true
end
after_commit :update_whois_record, unless: -> { domain_name.at_auction? }
after_commit :update_whois_record
after_create :update_reserved_domains
def update_reserved_domains

View file

@ -128,6 +128,7 @@ class Epp::Domain < Domain
def attach_legal_document(legal_document_data)
return unless legal_document_data
return unless legal_document_data[:body]
return if legal_document_data[:body].starts_with?(ENV['legal_documents_dir'])
legal_documents.create(

View file

@ -2,23 +2,34 @@ module Whois
class Record < Whois::Server
self.table_name = 'whois_records'
def self.without_auctions
ids = Whois::Record.all.select { |record| Auction.where(domain: record.name).blank? }
.pluck(:id)
Whois::Record.where(id: ids)
end
def self.disclaimer
Setting.registry_whois_disclaimer
end
# rubocop:disable Metrics/AbcSize
def update_from_auction(auction)
if auction.started?
update!(json: { name: auction.domain,
status: ['AtAuction'],
disclaimer: self.class.disclaimer })
ToStdout.msg "Updated from auction WHOIS record #{inspect}"
elsif auction.no_bids?
ToStdout.msg "Destroying WHOIS record #{inspect}"
destroy!
elsif auction.awaiting_payment? || auction.payment_received?
update!(json: { name: auction.domain,
status: ['PendingRegistration'],
disclaimer: self.class.disclaimer,
registration_deadline: auction.whois_deadline })
ToStdout.msg "Updated from auction WHOIS record #{inspect}"
end
end
# rubocop:enable Metrics/AbcSize
end
end

View file

@ -97,7 +97,7 @@ class WhoisRecord < ApplicationRecord
end
def destroy_whois_record
Whois::Record.where(name: name).delete_all
Whois::Record.without_auctions.where(name: name).delete_all
end
private

View file

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

View file

@ -5,6 +5,7 @@ module I18n
alias_method :original_localize, :localize
def localize(object, options = {})
options.merge!({ default: '-' })
object.present? ? original_localize(object, options) : ''
end
end

View file

@ -57,6 +57,15 @@ class RegistrantApiContactsTest < ApplicationIntegrationTest
assert_equal({ errors: [base: ['Not authorized']] }, json_body)
end
def test_gets_contact_domain_links_when_requested
get "/api/v1/registrant/contacts/#{@contact.uuid}?links=true", headers: @auth_headers
expected_links = @contact.domains.uniq.map { |d| { name: d.name, id: d.uuid }}
assert_response :ok
response_json = JSON.parse(response.body, symbolize_names: true)
assert_empty expected_links - response_json[:links]
end
private
def auth_token

View file

@ -2,6 +2,51 @@ require 'test_helper'
class EppDomainCreateBaseTest < EppTestCase
def test_illegal_chars_in_dns_key
name = "new.#{dns_zones(:one).origin}"
contact = contacts(:john)
registrant = contact.becomes(Registrant)
pub_key = "AwEAAddt2AkLf\n
\n
YGKgiEZB5SmIF8E\n
vrjxNMH6HtxW\rEA4RJ9Ao6LCWheg8"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<create>
<domain:create xmlns:domain="https://epp.tld.ee/schema/domain-eis-1.0.xsd">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
</domain:create>
</create>
<extension>
<secDNS:create xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:keyData>
<secDNS:flags>257</secDNS:flags>
<secDNS:protocol>3</secDNS:protocol>
<secDNS:alg>8</secDNS:alg>
<secDNS:pubKey>#{pub_key}</secDNS:pubKey>
</secDNS:keyData>
</secDNS:create>
<eis:extdata xmlns:eis="https://epp.tld.ee/schema/eis-1.0.xsd">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_no_difference 'Domain.count' do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
assert_epp_response :parameter_value_syntax_error
end
def test_not_registers_domain_without_legaldoc
now = Time.zone.parse('2010-07-05')
travel_to now

View file

@ -3,6 +3,7 @@ require 'test_helper'
class EppDomainTransferRequestTest < EppTestCase
def setup
@domain = domains(:shop)
@contact = contacts(:jane)
@new_registrar = registrars(:goodnames)
@original_transfer_wait_time = Setting.transfer_wait_time
Setting.transfer_wait_time = 0
@ -12,6 +13,95 @@ class EppDomainTransferRequestTest < EppTestCase
Setting.transfer_wait_time = @original_transfer_wait_time
end
def test_transfer_domain_with_contacts_if_registrant_and_tech_are_shared
@domain.tech_domain_contacts[0].update!(contact_id: @domain.registrant.id)
@domain.tech_domain_contacts[1].delete
@domain.reload
post epp_transfer_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_goodnames' }
assert_epp_response :completed_successfully
@domain.reload
tech = Contact.find_by(id: @domain.tech_domain_contacts[0].contact_id)
assert_equal @domain.contacts.where(original_id: @domain.registrant.original_id).count, 1
assert_equal tech.registrar_id, @domain.registrar.id
end
def test_transfer_domain_with_contacts_if_registrant_and_admin_are_shared
@domain.admin_domain_contacts[0].update!(contact_id: @domain.registrant.id)
@domain.tech_domain_contacts[0].update!(contact_id: @contact.id)
@domain.tech_domain_contacts[1].delete
@domain.reload
post epp_transfer_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_goodnames' }
assert_epp_response :completed_successfully
@domain.reload
admin = Contact.find_by(id: @domain.admin_domain_contacts[0].contact_id)
assert_equal @domain.contacts.where(original_id: @domain.registrant.original_id).count, 1
assert_equal admin.registrar_id, @domain.registrar.id
end
def test_transfer_domain_with_contacts_if_admin_and_tech_are_shared
@domain.admin_domain_contacts[0].update!(contact_id: @contact.id)
@domain.tech_domain_contacts[0].update!(contact_id: @contact.id)
@domain.tech_domain_contacts[1].delete
@domain.reload
post epp_transfer_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_goodnames' }
assert_epp_response :completed_successfully
@domain.reload
admin = Contact.find_by(id: @domain.admin_domain_contacts[0].contact_id)
tech = Contact.find_by(id: @domain.tech_domain_contacts[0].contact_id)
result_hash = @domain.contacts.pluck(:original_id).group_by(&:itself).transform_values(&:count)
assert result_hash[admin.original_id], 2
assert_equal admin.registrar_id, @domain.registrar.id
assert_equal tech.registrar_id, @domain.registrar.id
end
def test_transfer_domain_with_contacts_if_admin_and_tech_and_registrant_are_shared
@domain.tech_domain_contacts[0].update!(contact_id: @domain.registrant.id)
@domain.admin_domain_contacts[0].update!(contact_id: @domain.registrant.id)
@domain.tech_domain_contacts[1].delete
@domain.reload
post epp_transfer_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_goodnames' }
assert_epp_response :completed_successfully
@domain.reload
admin = Contact.find_by(id: @domain.admin_domain_contacts[0].contact_id)
tech = Contact.find_by(id: @domain.tech_domain_contacts[0].contact_id)
assert_equal @domain.contacts.where(original_id: @domain.registrant.original_id).count, 2
result_hash = @domain.contacts.pluck(:original_id).group_by(&:itself).transform_values(&:count)
assert result_hash[@domain.registrant.original_id], 2
assert_equal admin.registrar_id, @domain.registrar.id
assert_equal tech.registrar_id, @domain.registrar.id
end
def test_transfers_domain_at_once
post epp_transfer_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_goodnames' }

View file

@ -38,4 +38,17 @@ class ReppV1DomainsTransferInfoTest < ActionDispatch::IntegrationTest
assert_equal 'Authorization error', json[:message]
assert_empty json[:data]
end
def test_processes_puny_domains
@domain.update(name_puny: 'xn--prototp-s2aa.ee')
headers = @auth_headers
headers['Auth-Code'] = @domain.transfer_code
get "/repp/v1/domains/xn--prototp-s2aa.ee/transfer_info", headers: headers
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
end
end

View file

@ -25,6 +25,7 @@ class DomainReleasableAuctionableTest < ActiveSupport::TestCase
def test_skips_auction_when_domains_is_blocked
assert_equal 'shop.test', @domain.name
blocked_domains(:one).update!(name: 'shop.test')
@domain.save!(validate: false)
@domain.release
@ -34,6 +35,7 @@ class DomainReleasableAuctionableTest < ActiveSupport::TestCase
def test_skips_auction_when_domains_is_reserved
assert_equal 'shop.test', @domain.name
reserved_domains(:one).update!(name: 'shop.test')
@domain.save!(validate: false)
@domain.release
@ -58,6 +60,24 @@ class DomainReleasableAuctionableTest < ActiveSupport::TestCase
end
end
def test_updates_whois_server
@domain.update!(delete_date: '2010-07-04')
travel_to Time.zone.parse('2010-07-05')
old_whois = @domain.whois_record
Domain.release_domains
assert_raises ActiveRecord::RecordNotFound do
old_whois.reload
end
whois_record = Whois::Record.find_by(name: @domain.name)
json = { "name"=>@domain.name,
"status"=>["AtAuction"],
"disclaimer"=> Setting.registry_whois_disclaimer }
assert_equal whois_record.json, json
end
def test_notifies_registrar
@domain.update!(delete_date: '2010-07-04')
travel_to Time.zone.parse('2010-07-05')

View file

@ -20,6 +20,7 @@ class AdminAreaPricesTest < ApplicationSystemTestCase
fill_in 'Valid from', with: effective_date
click_on 'Create price'
assert_text 'Price has been created'
assert_text I18n.localize(effective_date)
end

View file

@ -0,0 +1,28 @@
require 'application_system_test_case'
class RegistrarAccountActivitiesTest < ApplicationSystemTestCase
setup do
@registrar = registrars(:bestnames)
sign_in users(:api_bestnames)
end
def test_show_account_activity_page
account_activities(:one).update(sum: "123.00")
visit registrar_account_activities_path
assert_text 'Account activity'
end
def test_download_account_activity
now = Time.zone.parse('2010-07-05 08:00')
travel_to now
account_activities(:one).update(sum: "123.00")
get registrar_account_activities_path(format: :csv)
assert_response :ok
assert_equal "text/csv", response.headers['Content-Type']
assert_equal %(attachment; filename="account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"; filename*=UTF-8''account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv),
response.headers['Content-Disposition']
assert_not_empty response.body
end
end