Merge pull request #1574 from internetee/269-dispute-list

Implement disputed logic for domains
This commit is contained in:
Timo Võhmar 2020-05-22 16:53:58 +03:00 committed by GitHub
commit 9308ed7b91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1073 additions and 76 deletions

View file

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Admin
class DisputesController < BaseController
load_and_authorize_resource
before_action :set_dispute, only: %i[show edit update delete]
# GET /admin/disputes
def index
params[:q] ||= {}
@disputes = sortable_dispute_query_for(Dispute.active.all, params[:q])
@closed_disputes = sortable_dispute_query_for(Dispute.closed.all, params[:q], closed: true)
end
# GET /admin/disputes/1
def show; end
# GET /admin/disputes/new
def new
@dispute = Dispute.new
end
# GET /admin/disputes/1/edit
def edit; end
# POST /admin/disputes
def create
@dispute = Dispute.new(dispute_params)
if @dispute.save
notice = 'Dispute was successfully created'
notice += @dispute.domain ? '.' : ' for domain that is not registered.'
redirect_to admin_disputes_url, notice: notice
else
render :new
end
end
# PATCH/PUT /admin/disputes/1
def update
if @dispute.update(dispute_params.except(:domain_name))
redirect_to admin_disputes_url, notice: 'Dispute was successfully updated.'
else
render :edit
end
end
# DELETE /admin/disputes/1
def delete
@dispute.close(initiator: 'Admin')
redirect_to admin_disputes_url, notice: 'Dispute was successfully closed.'
end
private
def sortable_dispute_query_for(disputes, query, closed: false)
@q = disputes.order(:domain_name).search(query)
disputes = @q.result.page(closed ? params[:closed_page] : params[:page])
return disputes.per(params[:results_per_page]) if params[:results_per_page].present?
disputes
end
# Use callbacks to share common setup or constraints between actions.
def set_dispute
@dispute = Dispute.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def dispute_params
params.require(:dispute).permit(:domain_name, :password, :starts_at, :comment)
end
end
end

View file

@ -92,7 +92,7 @@ module Epp
status: Auction.statuses[:payment_received]) status: Auction.statuses[:payment_received])
active_auction.domain_registered! active_auction.domain_registered!
end end
Dispute.close_by_domain(@domain.name)
render_epp_response '/epp/domains/create' render_epp_response '/epp/domains/create'
else else
handle_errors(@domain) handle_errors(@domain)
@ -103,21 +103,17 @@ module Epp
def update def update
authorize! :update, @domain, @password authorize! :update, @domain, @password
if @domain.update(params[:parsed_frame], current_user) updated = @domain.update(params[:parsed_frame], current_user)
if @domain.epp_pending_update.present? (handle_errors(@domain) && return) unless updated
render_epp_response '/epp/domains/success_pending'
else pending = @domain.epp_pending_update.present?
render_epp_response '/epp/domains/success' render_epp_response "/epp/domains/success#{'_pending' if pending}"
end
else
handle_errors(@domain)
end
end end
def delete def delete
authorize! :delete, @domain, @password authorize! :delete, @domain, @password
handle_errors(@domain) and return unless @domain.can_be_deleted? (handle_errors(@domain) && return) unless @domain.can_be_deleted?
if @domain.epp_destroy(params[:parsed_frame], current_user.id) if @domain.epp_destroy(params[:parsed_frame], current_user.id)
if @domain.epp_pending_delete.present? if @domain.epp_pending_delete.present?

View file

@ -4,6 +4,7 @@ class Registrant::DomainDeleteConfirmsController < RegistrantController
def show def show
return if params[:confirmed] || params[:rejected] return if params[:confirmed] || params[:rejected]
@domain = Domain.find(params[:id]) @domain = Domain.find(params[:id])
@domain = nil unless @domain.registrant_delete_confirmable?(params[:token]) @domain = nil unless @domain.registrant_delete_confirmable?(params[:token])
end end
@ -21,22 +22,23 @@ class Registrant::DomainDeleteConfirmsController < RegistrantController
initiator = current_registrant_user ? current_registrant_user.username : initiator = current_registrant_user ? current_registrant_user.username :
t(:user_not_authenticated) t(:user_not_authenticated)
if params[:rejected] confirmed = params[:confirmed] ? true : false
if @registrant_verification.domain_registrant_delete_reject!("email link #{initiator}") action = if confirmed
flash[:notice] = t(:registrant_domain_verification_rejected) @registrant_verification.domain_registrant_delete_reject!("email link #{initiator}")
redirect_to registrant_domain_delete_confirm_path(@domain.id, rejected: true) else
else @registrant_verification.domain_registrant_delete_confirm!("email link #{initiator}")
flash[:alert] = t(:registrant_domain_delete_rejected_failed) end
return render 'show'
end fail_msg = t("registrant_domain_delete_#{confirmed ? 'confirmed' : 'rejected'}_failed".to_sym)
elsif params[:confirmed] success_msg = t("registrant_domain_verification_#{confirmed ? 'confirmed' : 'rejected'}".to_sym)
if @registrant_verification.domain_registrant_delete_confirm!("email link #{initiator}")
flash[:notice] = t(:registrant_domain_verification_confirmed) flash[:alert] = action ? success_msg : fail_msg
redirect_to registrant_domain_delete_confirm_path(@domain.id, confirmed: true) (render 'show' && return) unless action
else
flash[:alert] = t(:registrant_domain_delete_confirmed_failed) if confirmed
return render 'show' redirect_to registrant_domain_delete_confirm_path(@domain.id, confirmed: true) && return
end else
redirect_to registrant_domain_delete_confirm_path(@domain.id, rejected: true) unless confirmed
end end
end end
end end

View file

@ -31,6 +31,8 @@ class Registrant::DomainUpdateConfirmsController < RegistrantController
end end
elsif params[:confirmed] elsif params[:confirmed]
if @registrant_verification.domain_registrant_change_confirm!("email link, #{initiator}") if @registrant_verification.domain_registrant_change_confirm!("email link, #{initiator}")
Dispute.close_by_domain(@domain.name) if @domain.disputed?
flash[:notice] = t(:registrant_domain_verification_confirmed) flash[:notice] = t(:registrant_domain_verification_confirmed)
redirect_to registrant_domain_update_confirm_path(@domain.id, confirmed: true) redirect_to registrant_domain_update_confirm_path(@domain.id, confirmed: true)
else else

View file

@ -100,12 +100,14 @@ class Registrar
authorize! :update, Depp::Domain authorize! :update, Depp::Domain
@data = @domain.info(params[:domain_name]) @data = @domain.info(params[:domain_name])
@domain_params = Depp::Domain.construct_params_from_server_data(@data) @domain_params = Depp::Domain.construct_params_from_server_data(@data)
@dispute = Dispute.active.find_by(domain_name: params[:domain_name])
end end
def update def update
authorize! :update, Depp::Domain authorize! :update, Depp::Domain
@domain_params = params[:domain] @domain_params = params[:domain]
@data = @domain.update(@domain_params) @data = @domain.update(@domain_params)
@dispute = Dispute.active.find_by(domain_name: @domain_params[:name])
if response_ok? if response_ok?
redirect_to info_registrar_domains_url(domain_name: @domain_params[:name]) redirect_to info_registrar_domains_url(domain_name: @domain_params[:name])

View file

@ -0,0 +1,63 @@
class DisputeStatusUpdateJob < Que::Job
def run(logger: Logger.new(STDOUT))
@logger = logger
@backlog = { 'activated': 0, 'closed': 0, 'activate_fail': [], 'close_fail': [] }
.with_indifferent_access
close_disputes
activate_disputes
@logger.info "DisputeStatusUpdateJob - All done. Closed #{@backlog['closed']} and " \
"activated #{@backlog['activated']} disputes."
show_failed_disputes unless @backlog['activate_fail'].empty? && @backlog['close_fail'].empty?
end
def close_disputes
disputes = Dispute.where(closed: nil).where('expires_at < ?', Time.zone.today).all
@logger.info "DisputeStatusUpdateJob - Found #{disputes.count} closable disputes"
disputes.each do |dispute|
process_dispute(dispute, closing: true)
end
end
def activate_disputes
disputes = Dispute.where(closed: nil, starts_at: Time.zone.today).all
@logger.info "DisputeStatusUpdateJob - Found #{disputes.count} activatable disputes"
disputes.each do |dispute|
process_dispute(dispute, closing: false)
end
end
def process_dispute(dispute, closing: false)
intent = closing ? 'close' : 'activate'
success = closing ? dispute.close(initiator: 'Job') : dispute.generate_data
create_backlog_entry(dispute: dispute, intent: intent, successful: success)
end
def create_backlog_entry(dispute:, intent:, successful:)
if successful
@backlog["#{intent}d"] += 1
@logger.info "DisputeStatusUpdateJob - #{intent}d dispute " \
" for '#{dispute.domain_name}'"
else
@backlog["#{intent}_fail"] << dispute.id
@logger.info 'DisputeStatusUpdateJob - Failed to' \
"#{intent} dispute for '#{dispute.domain_name}'"
end
end
def show_failed_disputes
if @backlog['close_fail'].any?
@logger.info('DisputeStatusUpdateJob - Failed to close disputes with Ids:' \
"#{@backlog['close_fail']}")
end
return unless @backlog['activate_fail'].any?
@logger.info('DisputeStatusUpdateJob - Failed to activate disputes with Ids:' \
"#{@backlog['activate_fail']}")
end
end

View file

@ -1,13 +1,14 @@
class UpdateWhoisRecordJob < Que::Job class UpdateWhoisRecordJob < Que::Job
def run(names, type) def run(names, type)
::PaperTrail.whodunnit = "job - #{self.class.name} - #{type}" ::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{type}"
klass = case type klass = case type
when 'reserved'then ReservedDomain when 'reserved' then ReservedDomain
when 'blocked' then BlockedDomain when 'blocked' then BlockedDomain
when 'domain' then Domain when 'domain' then Domain
end when 'disputed' then Dispute.active
end
Array(names).each do |name| Array(names).each do |name|
record = klass.find_by(name: name) record = klass.find_by(name: name)
@ -19,8 +20,6 @@ class UpdateWhoisRecordJob < Que::Job
end end
end end
def update_domain(domain) def update_domain(domain)
domain.whois_record ? domain.whois_record.save : domain.create_whois_record domain.whois_record ? domain.whois_record.save : domain.create_whois_record
end end
@ -33,6 +32,9 @@ class UpdateWhoisRecordJob < Que::Job
update_reserved(record) update_reserved(record)
end end
def update_disputed(record)
update_reserved(record)
end
# 1. deleting own # 1. deleting own
# 2. trying to regenerate reserved in order domain is still in the list # 2. trying to regenerate reserved in order domain is still in the list
@ -41,14 +43,27 @@ class UpdateWhoisRecordJob < Que::Job
BlockedDomain.find_by(name: name).try(:generate_data) BlockedDomain.find_by(name: name).try(:generate_data)
ReservedDomain.find_by(name: name).try(:generate_data) ReservedDomain.find_by(name: name).try(:generate_data)
Dispute.active.find_by(domain_name: name).try(:generate_data)
end end
def delete_reserved(name) def delete_reserved(name)
Domain.where(name: name).any? remove_status_from_whois(domain_name: name, domain_status: 'Reserved')
Whois::Record.where(name: name).delete_all
end end
def delete_blocked(name) def delete_blocked(name)
delete_reserved(name) delete_reserved(name)
end end
def delete_disputed(name)
return if Dispute.active.find_by(domain_name: name).present?
remove_status_from_whois(domain_name: name, domain_status: 'disputed')
end
def remove_status_from_whois(domain_name:, domain_status:)
Whois::Record.where(name: domain_name).each do |r|
r.json['status'] = r.json['status'].delete_if { |status| status == domain_status }
r.json['status'].blank? ? r.destroy : r.save
end
end
end end

View file

@ -100,6 +100,7 @@ class Ability
can :manage, Invoice can :manage, Invoice
can :manage, WhiteIp can :manage, WhiteIp
can :manage, AccountActivity can :manage, AccountActivity
can :manage, Dispute
can :read, ApiLog::EppLog can :read, ApiLog::EppLog
can :read, ApiLog::ReppLog can :read, ApiLog::ReppLog
can :update, :pending can :update, :pending

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
module Concerns
module Domain
module Disputable
extend ActiveSupport::Concern
included do
validate :validate_disputed
end
def mark_as_disputed
statuses.push(DomainStatus::DISPUTED) unless statuses.include?(DomainStatus::DISPUTED)
save
end
def unmark_as_disputed
statuses.delete_if { |status| status == DomainStatus::DISPUTED }
save
end
def in_disputed_list?
@in_disputed_list ||= Dispute.active.find_by(domain_name: name).present?
end
def disputed?
Dispute.active.where(domain_name: name).any?
end
def validate_disputed
return if persisted? || !in_disputed_list?
if reserved_pw.blank?
errors.add(:base, :required_parameter_missing_disputed)
return false
end
return if Dispute.valid_auth?(name, reserved_pw)
errors.add(:base, :invalid_auth_information_reserved)
end
end
end
end

View file

@ -0,0 +1,15 @@
module WhoisStatusPopulate
extend ActiveSupport::Concern
def generate_json(record, domain_status:)
h = HashWithIndifferentAccess.new(name: record.name, status: [domain_status])
return h if record.json.blank?
status_arr = (record.json['status'] ||= [])
return record.json if status_arr.include? domain_status
status_arr.push(domain_status)
record.json['status'] = status_arr
record.json
end
end

133
app/models/dispute.rb Normal file
View file

@ -0,0 +1,133 @@
class Dispute < ApplicationRecord
include WhoisStatusPopulate
validates :domain_name, :password, :starts_at, :expires_at, presence: true
before_validation :fill_empty_passwords, :set_expiry_date
validate :validate_domain_name_format
validate :validate_domain_name_period_uniqueness
validate :validate_start_date
before_save :set_expiry_date, :sync_reserved_password, :generate_data
after_destroy :remove_data
scope :expired, -> { where('expires_at < ?', Time.zone.today) }
scope :active, lambda {
where('starts_at <= ? AND expires_at >= ? AND closed IS NULL', Time.zone.today, Time.zone.today)
}
scope :closed, -> { where.not(closed: nil) }
attr_readonly :domain_name
def domain
Domain.find_by(name: domain_name)
end
def self.close_by_domain(domain_name)
dispute = Dispute.active.find_by(domain_name: domain_name)
return false unless dispute
dispute.close(initiator: 'Registrant')
end
def self.valid_auth?(domain_name, password)
Dispute.active.find_by(domain_name: domain_name, password: password).present?
end
def set_expiry_date
return if starts_at.blank?
self.expires_at = starts_at + Setting.dispute_period_in_months.months
end
def generate_password
self.password = SecureRandom.hex
end
def generate_data
return if starts_at > Time.zone.today || expires_at < Time.zone.today
domain&.mark_as_disputed
return if domain
wr = Whois::Record.find_or_initialize_by(name: domain_name)
wr.json = @json = generate_json(wr, domain_status: 'disputed')
wr.save
end
def close(initiator: 'Unknown')
return false unless update(closed: Time.zone.now, initiator: initiator)
return if Dispute.active.where(domain_name: domain_name).any?
domain&.unmark_as_disputed
return true if domain
forward_to_auction_if_possible
end
def forward_to_auction_if_possible
domain = DNS::DomainName.new(domain_name)
if domain.available? && domain.auctionable?
domain.sell_at_auction
return true
end
whois_record = Whois::Record.find_by(name: domain_name)
remove_whois_data(whois_record)
end
def remove_whois_data(record)
return true unless record
record.json['status'] = record.json['status'].delete_if { |status| status == 'disputed' }
record.destroy && return if record.json['status'].blank?
record.save
end
def remove_data
UpdateWhoisRecordJob.enqueue domain_name, 'disputed'
end
def fill_empty_passwords
generate_password if password.blank?
end
def sync_reserved_password
reserved_domain = ReservedDomain.find_by(name: domain_name)
generate_password if password.blank?
unless reserved_domain.nil?
reserved_domain.password = password
reserved_domain.save!
end
generate_data
end
private
def validate_start_date
return if starts_at.nil?
errors.add(:starts_at, :future) if starts_at.future?
end
def validate_domain_name_format
return unless domain_name
zone = domain_name.reverse.rpartition('.').map(&:reverse).reverse.last
supported_zone = DNS::Zone.origins.include?(zone)
errors.add(:domain_name, :unsupported_zone) unless supported_zone
end
def validate_domain_name_period_uniqueness
existing_dispute = Dispute.unscoped.where(domain_name: domain_name, closed: nil)
.where('expires_at >= ?', starts_at)
existing_dispute = existing_dispute.where.not(id: id) unless new_record?
return unless existing_dispute.any?
errors.add(:starts_at, 'Dispute already exists for this domain at given timeframe')
end
end

View file

@ -68,6 +68,10 @@ module DNS
ReservedDomain.where(name: name).any? ReservedDomain.where(name: name).any?
end end
def disputed?
Dispute.active.where(domain_name: name).any?
end
def auctionable? def auctionable?
!not_auctionable? !not_auctionable?
end end
@ -81,7 +85,7 @@ module DNS
attr_reader :name attr_reader :name
def not_auctionable? def not_auctionable?
blocked? || reserved? blocked? || reserved? || disputed?
end end
def zone_with_same_origin? def zone_with_same_origin?

View file

@ -9,6 +9,7 @@ class Domain < ApplicationRecord
include Concerns::Domain::Transferable include Concerns::Domain::Transferable
include Concerns::Domain::RegistryLockable include Concerns::Domain::RegistryLockable
include Concerns::Domain::Releasable include Concerns::Domain::Releasable
include Concerns::Domain::Disputable
attr_accessor :roles attr_accessor :roles
@ -88,8 +89,8 @@ class Domain < ApplicationRecord
validates :puny_label, length: { maximum: 63 } validates :puny_label, length: { maximum: 63 }
validates :period, presence: true, numericality: { only_integer: true } validates :period, presence: true, numericality: { only_integer: true }
validates :transfer_code, presence: true validates :transfer_code, presence: true
validate :validate_reservation validate :validate_reservation
def validate_reservation def validate_reservation
return if persisted? || !in_reserved_list? return if persisted? || !in_reserved_list?
@ -99,6 +100,7 @@ class Domain < ApplicationRecord
end end
return if ReservedDomain.pw_for(name) == reserved_pw return if ReservedDomain.pw_for(name) == reserved_pw
errors.add(:base, :invalid_auth_information_reserved) errors.add(:base, :invalid_auth_information_reserved)
end end
@ -282,20 +284,23 @@ class Domain < ApplicationRecord
def server_holdable? def server_holdable?
return false if statuses.include?(DomainStatus::SERVER_HOLD) return false if statuses.include?(DomainStatus::SERVER_HOLD)
return false if statuses.include?(DomainStatus::SERVER_MANUAL_INZONE) return false if statuses.include?(DomainStatus::SERVER_MANUAL_INZONE)
true true
end end
def renewable? def renewable?
if Setting.days_to_renew_domain_before_expire != 0 blocking_statuses = [DomainStatus::DELETE_CANDIDATE, DomainStatus::PENDING_RENEW,
# if you can renew domain at days_to_renew before domain expiration DomainStatus::PENDING_TRANSFER, DomainStatus::DISPUTED,
if (expire_time.to_date - Date.today) + 1 > Setting.days_to_renew_domain_before_expire DomainStatus::PENDING_UPDATE, DomainStatus::PENDING_DELETE,
return false DomainStatus::PENDING_DELETE_CONFIRMATION]
end return false if statuses.include_any? blocking_statuses
return true unless Setting.days_to_renew_domain_before_expire != 0
# if you can renew domain at days_to_renew before domain expiration
if (expire_time.to_date - Time.zone.today) + 1 > Setting.days_to_renew_domain_before_expire
return false
end end
return false if statuses.include_any?(DomainStatus::DELETE_CANDIDATE, DomainStatus::PENDING_RENEW,
DomainStatus::PENDING_TRANSFER, DomainStatus::PENDING_DELETE,
DomainStatus::PENDING_UPDATE, DomainStatus::PENDING_DELETE_CONFIRMATION)
true true
end end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
class DomainStatus < ApplicationRecord class DomainStatus < ApplicationRecord
include EppErrors include EppErrors
belongs_to :domain belongs_to :domain
@ -70,6 +72,7 @@ class DomainStatus < ApplicationRecord
FORCE_DELETE = 'serverForceDelete' FORCE_DELETE = 'serverForceDelete'
DELETE_CANDIDATE = 'deleteCandidate' DELETE_CANDIDATE = 'deleteCandidate'
EXPIRED = 'expired' EXPIRED = 'expired'
DISPUTED = 'disputed'
STATUSES = [ STATUSES = [
CLIENT_DELETE_PROHIBITED, SERVER_DELETE_PROHIBITED, CLIENT_HOLD, SERVER_HOLD, CLIENT_DELETE_PROHIBITED, SERVER_DELETE_PROHIBITED, CLIENT_HOLD, SERVER_HOLD,
@ -78,19 +81,19 @@ class DomainStatus < ApplicationRecord
INACTIVE, OK, PENDING_CREATE, PENDING_DELETE, PENDING_DELETE_CONFIRMATION, PENDING_RENEW, PENDING_TRANSFER, INACTIVE, OK, PENDING_CREATE, PENDING_DELETE, PENDING_DELETE_CONFIRMATION, PENDING_RENEW, PENDING_TRANSFER,
PENDING_UPDATE, SERVER_MANUAL_INZONE, SERVER_REGISTRANT_CHANGE_PROHIBITED, PENDING_UPDATE, SERVER_MANUAL_INZONE, SERVER_REGISTRANT_CHANGE_PROHIBITED,
SERVER_ADMIN_CHANGE_PROHIBITED, SERVER_TECH_CHANGE_PROHIBITED, FORCE_DELETE, SERVER_ADMIN_CHANGE_PROHIBITED, SERVER_TECH_CHANGE_PROHIBITED, FORCE_DELETE,
DELETE_CANDIDATE, EXPIRED DELETE_CANDIDATE, EXPIRED, DISPUTED
] ].freeze
CLIENT_STATUSES = [ CLIENT_STATUSES = [
CLIENT_DELETE_PROHIBITED, CLIENT_HOLD, CLIENT_RENEW_PROHIBITED, CLIENT_TRANSFER_PROHIBITED, CLIENT_DELETE_PROHIBITED, CLIENT_HOLD, CLIENT_RENEW_PROHIBITED, CLIENT_TRANSFER_PROHIBITED,
CLIENT_UPDATE_PROHIBITED CLIENT_UPDATE_PROHIBITED
] ].freeze
SERVER_STATUSES = [ SERVER_STATUSES = [
SERVER_DELETE_PROHIBITED, SERVER_HOLD, SERVER_RENEW_PROHIBITED, SERVER_TRANSFER_PROHIBITED, SERVER_DELETE_PROHIBITED, SERVER_HOLD, SERVER_RENEW_PROHIBITED, SERVER_TRANSFER_PROHIBITED,
SERVER_UPDATE_PROHIBITED, SERVER_MANUAL_INZONE, SERVER_REGISTRANT_CHANGE_PROHIBITED, SERVER_UPDATE_PROHIBITED, SERVER_MANUAL_INZONE, SERVER_REGISTRANT_CHANGE_PROHIBITED,
SERVER_ADMIN_CHANGE_PROHIBITED, SERVER_TECH_CHANGE_PROHIBITED SERVER_ADMIN_CHANGE_PROHIBITED, SERVER_TECH_CHANGE_PROHIBITED
] ].freeze
UPDATE_PROHIBIT_STATES = [ UPDATE_PROHIBIT_STATES = [
DomainStatus::PENDING_DELETE_CONFIRMATION, DomainStatus::PENDING_DELETE_CONFIRMATION,

View file

@ -53,12 +53,13 @@ class Epp::Domain < Domain
def epp_code_map def epp_code_map
{ {
'2002' => [ # Command use error '2002' => [ # Command use error
[:base, :domain_already_belongs_to_the_querying_registrar] %i[base domain_already_belongs_to_the_querying_registrar],
], ],
'2003' => [ # Required parameter missing '2003' => [ # Required parameter missing
[:registrant, :blank], %i[registrant blank],
[:registrar, :blank], %i[registrar blank],
[:base, :required_parameter_missing_reserved] %i[base required_parameter_missing_reserved],
%i[base required_parameter_missing_disputed],
], ],
'2004' => [ # Parameter value range error '2004' => [ # Parameter value range error
[:dnskeys, :out_of_range, [:dnskeys, :out_of_range,
@ -85,10 +86,11 @@ class Epp::Domain < Domain
[:puny_label, :too_long, { obj: 'name', val: name_puny }] [:puny_label, :too_long, { obj: 'name', val: name_puny }]
], ],
'2201' => [ # Authorisation error '2201' => [ # Authorisation error
[:transfer_code, :wrong_pw] %i[transfer_code wrong_pw],
], ],
'2202' => [ '2202' => [
[:base, :invalid_auth_information_reserved] %i[base invalid_auth_information_reserved],
%i[base invalid_auth_information_disputed],
], ],
'2302' => [ # Object exists '2302' => [ # Object exists
[:name_dirty, :taken, { value: { obj: 'name', val: name_dirty } }], [:name_dirty, :taken, { value: { obj: 'name', val: name_dirty } }],
@ -473,13 +475,35 @@ class Epp::Domain < Domain
self.up_date = Time.zone.now self.up_date = Time.zone.now
end end
same_registrant_as_current = (registrant.code == frame.css('registrant').text) same_registrant_as_current = true
# registrant block may not be present, so we need this to rule out false positives
if frame.css('registrant').text.present?
same_registrant_as_current = (registrant.code == frame.css('registrant').text)
end
if !same_registrant_as_current && 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)
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 !same_registrant_as_current && errors.empty? && verify && if !same_registrant_as_current && errors.empty? && verify &&
Setting.request_confrimation_on_registrant_change_enabled && Setting.request_confrimation_on_registrant_change_enabled &&
frame.css('registrant').present? && unverified_registrant_params
frame.css('registrant').attr('verified').to_s.downcase != 'yes' registrant_verification_asked!(frame.to_s, current_user.id) unless disputed?
registrant_verification_asked!(frame.to_s, current_user.id)
end end
errors.empty? && super(at) errors.empty? && super(at)
@ -706,6 +730,11 @@ class Epp::Domain < Domain
def can_be_deleted? def can_be_deleted?
if disputed?
errors.add(:base, :domain_status_prohibits_operation)
return false
end
begin begin
errors.add(:base, :domain_status_prohibits_operation) errors.add(:base, :domain_status_prohibits_operation)
return false return false

View file

@ -1,7 +1,9 @@
class ReservedDomain < ApplicationRecord class ReservedDomain < ApplicationRecord
include Versions # version/reserved_domain_version.rb include Versions # version/reserved_domain_version.rb
include WhoisStatusPopulate
before_save :fill_empty_passwords before_save :fill_empty_passwords
before_save :generate_data before_save :generate_data
before_save :sync_dispute_password
after_destroy :remove_data after_destroy :remove_data
validates :name, domain_name: true, uniqueness: true validates :name, domain_name: true, uniqueness: true
@ -41,23 +43,21 @@ class ReservedDomain < ApplicationRecord
self.password = SecureRandom.hex self.password = SecureRandom.hex
end end
def sync_dispute_password
dispute = Dispute.active.find_by(domain_name: name)
self.password = dispute.password if dispute.present?
end
def generate_data def generate_data
return if Domain.where(name: name).any? return if Domain.where(name: name).any?
wr = Whois::Record.find_or_initialize_by(name: name) wr = Whois::Record.find_or_initialize_by(name: name)
wr.json = @json = generate_json # we need @json to bind to class wr.json = @json = generate_json(wr, domain_status: 'Reserved') # we need @json to bind to class
wr.save wr.save
end end
alias_method :update_whois_record, :generate_data alias_method :update_whois_record, :generate_data
def generate_json
h = HashWithIndifferentAccess.new
h[:name] = self.name
h[:status] = ['Reserved']
h
end
def remove_data def remove_data
UpdateWhoisRecordJob.enqueue name, 'reserved' UpdateWhoisRecordJob.enqueue name, 'reserved'
end end

View file

@ -46,6 +46,7 @@ class Setting < RailsSettings::Base
expire_warning_period expire_warning_period
redemption_grace_period redemption_grace_period
expire_pending_confirmation expire_pending_confirmation
dispute_period_in_months
] ]
end end

View file

@ -84,6 +84,7 @@ class WhoisRecord < ApplicationRecord
def populate def populate
return if domain_id.blank? return if domain_id.blank?
self.json = generated_json self.json = generated_json
self.name = json['name'] self.name = json['name']
self.registrar_id = domain.registrar_id if domain # for faster registrar updates self.registrar_id = domain.registrar_id if domain # for faster registrar updates

View file

@ -32,10 +32,11 @@
%li= link_to t('.zones'), admin_zones_path %li= link_to t('.zones'), admin_zones_path
%li= link_to t('.blocked_domains'), admin_blocked_domains_path %li= link_to t('.blocked_domains'), admin_blocked_domains_path
%li= link_to t('.reserved_domains'), admin_reserved_domains_path %li= link_to t('.reserved_domains'), admin_reserved_domains_path
%li= link_to t('.disputed_domains'), admin_disputes_path
%li= link_to t('.epp_log'), admin_epp_logs_path(created_after: 'today') %li= link_to t('.epp_log'), admin_epp_logs_path(created_after: 'today')
%li= link_to t('.repp_log'), admin_repp_logs_path(created_after: 'today') %li= link_to t('.repp_log'), admin_repp_logs_path(created_after: 'today')
%li= link_to t('.que'), '/admin/que' %li= link_to t('.que'), '/admin/que'
%ul.nav.navbar-nav.navbar-right %ul.nav.navbar-nav.navbar-right
%li= link_to t('.sign_out'), destroy_admin_user_session_path, method: :delete, %li= link_to t('.sign_out'), destroy_admin_user_session_path, method: :delete,
class: 'navbar-link' class: 'navbar-link'

View file

@ -0,0 +1,60 @@
<%= form_for([:admin, @dispute], html: { class: 'form-horizontal' }) do |f| %>
<%= render 'shared/full_errors', object: @dispute %>
<div class="row">
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="pull-left">
<%= t(:general) %>
</div>
</div>
<div class="panel-body">
<div>
<p>As per domain law, expiry time is <%= Setting.dispute_period_in_months / 12 %> years ahead from start date.</p>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :domain_name %>
</div>
<div class="col-md-7">
<%= f.text_field(:domain_name, class: 'form-control', disabled: !f.object.new_record?) %>
</div>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :password %>
</div>
<div class="col-md-7">
<%= f.text_field(:password, placeholder: t(:optional), class: 'form-control') %>
<span class="help-block"><%= t '.password_hint' %></span>
</div>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :starts_at %>
</div>
<div class="col-md-7">
<%= f.text_field(:starts_at, class: 'form-control js-datepicker') %>
<span class="help-block"><%= t '.past_or_today' %></span>
</div>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :comment %>
</div>
<div class="col-md-7">
<%= f.text_field(:comment, placeholder: t(:optional), class: 'form-control') %>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 text-right">
<%= button_tag(t(:save), class: 'btn btn-primary') %>
</div>
</div>
<% end %>

View file

@ -0,0 +1,3 @@
= render 'shared/title', name: t(:edit_dispute)
= render 'form'

View file

@ -0,0 +1,175 @@
<% content_for :actions do %>
<%= link_to(t('.new_btn'), new_admin_dispute_path, class: 'btn btn-primary') %>
<% end %>
<%= render 'shared/title', name: t('.title') %>
<div class="row">
<div class="col-md-12">
<%= search_form_for [:admin, @q], html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f| %>
<div class="row">
<div class="col-md-3">
<div class="form-group">
<%= f.label :domain_name %>
<%= f.search_field :domain_name_matches, value: params[:q][:domain_name_matches], class: 'form-control', placeholder: t(:name) %>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<%= f.label t(:created_at_from) %>
<%= f.search_field :created_at_gteq, value: params[:q][:created_at_gteq], class: 'form-control js-datepicker', placeholder: t(:created_at_from) %>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<%= f.label t(:created_at_until) %>
<%= f.search_field :created_at_lteq, value: params[:q][:created_at_lteq], class: 'form-control js-datepicker', placeholder: t(:created_at_until) %>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="form-group">
<%= label_tag t(:results_per_page) %>
<%= text_field_tag :results_per_page, params[:results_per_page], class: 'form-control', placeholder: t(:results_per_page) %>
</div>
</div>
<div class="col-md-3" style="padding-top: 25px;">
<button class="btn btn-primary">
&nbsp;
<span class="glyphicon glyphicon-search"></span>
&nbsp;
</button>
<%= link_to(t('.reset_btn'), admin_disputes_path, class: 'btn btn-default') %>
</div>
</div>
<% end %>
</div>
</div>
<hr />
<p>Active disputes</p>
<div class="row">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-hover table-bordered table-condensed">
<thead>
<tr>
<th class="col-xs-2">
<%= sort_link(@q, 'domain_name') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'password') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'starts_at') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'expires_at') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'comment') %>
</th>
<th class="col-xs-2">
<%= t(:actions) %>
</th>
</tr>
</thead>
<tbody>
<% @disputes.each do |x| %>
<tr>
<td>
<%= x.domain_name %>
</td>
<td>
<%= x.password %>
</td>
<td>
<%= x.starts_at %>
</td>
<td>
<%= x.expires_at %>
</td>
<td>
<%= x.comment %>
</td>
<td>
<%= link_to t(:edit), edit_admin_dispute_path(id: x.id),
class: 'btn btn-primary btn-xs' %>
<%= link_to t(:delete), delete_admin_dispute_path(id: x.id),
data: { confirm: t(:are_you_sure) }, class: 'btn btn-danger btn-xs' %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= paginate @disputes %>
</div>
<div class="col-md-6 text-right">
<div class="pagination">
<%= t(:result_count, count: @disputes.total_count) %>
</div>
</div>
</div>
<hr />
<p>Expired / Closed disputes</p>
<div class="row">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-hover table-bordered table-condensed">
<thead>
<tr>
<th class="col-xs-2">
<%= sort_link(@q, 'domain_name') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'initiator') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'starts_at') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'closed') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'comment') %>
</th>
</tr>
</thead>
<tbody>
<% @closed_disputes.each do |x| %>
<tr>
<td>
<%= x.domain_name %>
</td>
<td>
<%= x.initiator %>
</td>
<td>
<%= x.starts_at %>
</td>
<td>
<%= x.closed %>
</td>
<td>
<%= x.comment %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= paginate @closed_disputes, param_name: :closed_page %>
</div>
<div class="col-md-6 text-right">
<div class="pagination">
<%= t(:result_count, count: @closed_disputes.total_count) %>
</div>
</div>
</div>

View file

@ -0,0 +1,3 @@
= render 'shared/title', name: t(:add_disputed_domain)
= render 'form'

View file

@ -48,7 +48,7 @@
= render 'setting_row', var: :request_confrimation_on_registrant_change_enabled = render 'setting_row', var: :request_confrimation_on_registrant_change_enabled
= render 'setting_row', var: :request_confirmation_on_domain_deletion_enabled = render 'setting_row', var: :request_confirmation_on_domain_deletion_enabled
= render 'setting_row', var: :address_processing = render 'setting_row', var: :address_processing
= render 'setting_row', var: :dispute_period_in_months
%tr %tr
%td.col-md-6= label_tag :default_language %td.col-md-6= label_tag :default_language
%td.col-md-6 %td.col-md-6

View file

@ -31,7 +31,7 @@
.col-md-7 .col-md-7
= check_box_tag 'domain[verified]', '1', @domain_params[:verified].eql?('1'), onclick: "return (confirm('#{t(:verified_confirm)}') ? true : false);" = check_box_tag 'domain[verified]', '1', @domain_params[:verified].eql?('1'), onclick: "return (confirm('#{t(:verified_confirm)}') ? true : false);"
- unless params[:domain_name] - if !params[:domain_name] || @dispute.present?
.form-group .form-group
.col-md-3.control-label .col-md-3.control-label
= label_tag :domain_reserved_pw, t(:reserved_pw) = label_tag :domain_reserved_pw, t(:reserved_pw)

View file

@ -44,6 +44,8 @@ defaults: &defaults
registrar_ip_whitelist_enabled: false registrar_ip_whitelist_enabled: false
api_ip_whitelist_enabled: false api_ip_whitelist_enabled: false
dispute_period_in_months: 36
registry_juridical_name: "Eesti Interneti SA" registry_juridical_name: "Eesti Interneti SA"
registry_reg_no: "90010019" registry_reg_no: "90010019"
registry_email: "info@internet.ee" registry_email: "info@internet.ee"

View file

@ -0,0 +1,19 @@
en:
activerecord:
errors:
models:
dispute:
attributes:
starts_at:
future: 'can not be greater than today'
admin:
disputes:
index:
title: Disputed domains
new_btn: New disputed domain
reset_btn: Reset
form:
password_hint: Generated automatically if left blank
optional: Not required by default
past_or_today: Can not be greater than today's date

View file

@ -13,6 +13,7 @@ en:
zones: Zones zones: Zones
blocked_domains: Blocked domains blocked_domains: Blocked domains
reserved_domains: Reserved domains reserved_domains: Reserved domains
disputed_domains: Disputed domains
epp_log: EPP log epp_log: EPP log
repp_log: REPP log repp_log: REPP log
que: Que que: Que

View file

@ -24,6 +24,8 @@ en:
key_data_not_allowed: 'keyData object is not allowed' key_data_not_allowed: 'keyData object is not allowed'
required_parameter_missing_reserved: 'Required parameter missing; reserved>pw element required for reserved domains' required_parameter_missing_reserved: 'Required parameter missing; reserved>pw element required for reserved domains'
invalid_auth_information_reserved: 'Invalid authorization information; invalid reserved>pw value' invalid_auth_information_reserved: 'Invalid authorization information; invalid reserved>pw value'
required_parameter_missing_disputed: 'Required parameter missing; disputed pw element required for dispute domains'
invalid_auth_information_disputed: 'Invalid authorization information; invalid disputed>pw value'
domain_name_blocked: 'Data management policy violation: Domain name is blocked [name]' domain_name_blocked: 'Data management policy violation: Domain name is blocked [name]'
name_dirty: name_dirty:
invalid: 'Domain name is invalid' invalid: 'Domain name is invalid'
@ -629,7 +631,9 @@ en:
available_verification_url_not_found: 'Available verification url not found, for domain.' available_verification_url_not_found: 'Available verification url not found, for domain.'
add_reserved_domain: 'Add domain to reserved list' add_reserved_domain: 'Add domain to reserved list'
add_blocked_domain: 'Add domain to blocked list' add_blocked_domain: 'Add domain to blocked list'
add_disputed_domain: 'Add domain to disputed list'
edit_pw: 'Edit Pw' edit_pw: 'Edit Pw'
edit_dispute: 'Edit dispute'
optional: 'Optional' optional: 'Optional'
test_registrar: "Test registrar" test_registrar: "Test registrar"
verified_confirm: 'Verified status is for cases when current registrant is the one applying for the update. Legal document signed by the registrant is required. Are you sure this update is properly verified with the registrant?' verified_confirm: 'Verified status is for cases when current registrant is the one applying for the update. Legal document signed by the registrant is required. Are you sure this update is properly verified with the registrant?'

View file

@ -271,6 +271,11 @@ Rails.application.routes.draw do
get 'delete' get 'delete'
end end
end end
resources :disputes do
member do
get 'delete'
end
end
resources :registrars do resources :registrars do
resources :api_users, except: %i[index] resources :api_users, except: %i[index]

View file

@ -0,0 +1,14 @@
class CreateDisputes < ActiveRecord::Migration[5.2]
def change
create_table :disputes do |t|
t.string :domain_name, null: false
t.string :password, null: false
t.date :expires_at, null: false
t.date :starts_at, null: false
t.text :comment
t.boolean :closed, null: false, default: false
t.timestamps
end
end
end

View file

@ -0,0 +1,5 @@
class AddDisputePeriodInMonthsToSetting < ActiveRecord::Migration[5.2]
def change
Setting.create(var: 'dispute_period_in_months', value: 36)
end
end

View file

@ -0,0 +1,19 @@
class AddClosedDateTimeAndUpdatorToDispute < ActiveRecord::Migration[5.2]
def up
rename_column :disputes, :closed, :closed_boolean
add_column :disputes, :closed, :datetime
execute 'UPDATE disputes SET closed = updated_at WHERE closed_boolean = true'
execute 'UPDATE disputes SET closed = NULL WHERE closed_boolean = false'
remove_column :disputes, :closed_boolean
add_column :disputes, :initiator, :string
end
def down
rename_column :disputes, :closed, :closed_datetime
add_column :disputes, :closed, :boolean, null: false, default: false
execute 'UPDATE disputes SET closed = true WHERE closed_datetime != NULL'
execute 'UPDATE disputes SET closed = false WHERE closed_datetime = NULL'
remove_column :disputes, :closed_datetime
remove_column :disputes, :initiator
end
end

View file

@ -1,6 +1,6 @@
-- ---
-- PostgreSQL database dump --- PostgreSQL database dump
-- ---
SET statement_timeout = 0; SET statement_timeout = 0;
SET lock_timeout = 0; SET lock_timeout = 0;
@ -594,6 +594,43 @@ CREATE SEQUENCE public.directos_id_seq
ALTER SEQUENCE public.directos_id_seq OWNED BY public.directos.id; ALTER SEQUENCE public.directos_id_seq OWNED BY public.directos.id;
--
-- Name: disputes; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE public.disputes (
id bigint NOT NULL,
domain_name character varying NOT NULL,
password character varying NOT NULL,
expires_at date NOT NULL,
starts_at date NOT NULL,
comment text,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
closed timestamp without time zone,
initiator character varying
);
--
-- Name: disputes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.disputes_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: disputes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.disputes_id_seq OWNED BY public.disputes.id;
-- --
-- Name: dnskeys; Type: TABLE; Schema: public; Owner: -; Tablespace: -- Name: dnskeys; Type: TABLE; Schema: public; Owner: -; Tablespace:
-- --
@ -2415,6 +2452,13 @@ ALTER TABLE ONLY public.contacts ALTER COLUMN id SET DEFAULT nextval('public.con
ALTER TABLE ONLY public.directos ALTER COLUMN id SET DEFAULT nextval('public.directos_id_seq'::regclass); ALTER TABLE ONLY public.directos ALTER COLUMN id SET DEFAULT nextval('public.directos_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.disputes ALTER COLUMN id SET DEFAULT nextval('public.disputes_id_seq'::regclass);
-- --
-- Name: id; Type: DEFAULT; Schema: public; Owner: - -- Name: id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -2811,6 +2855,14 @@ ALTER TABLE ONLY public.directos
ADD CONSTRAINT directos_pkey PRIMARY KEY (id); ADD CONSTRAINT directos_pkey PRIMARY KEY (id);
--
-- Name: disputes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
ALTER TABLE ONLY public.disputes
ADD CONSTRAINT disputes_pkey PRIMARY KEY (id);
-- --
-- Name: dnskeys_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- Name: dnskeys_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
-- --
@ -4467,5 +4519,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200204103125'), ('20200204103125'),
('20200311114649'), ('20200311114649'),
('20200417075720'), ('20200417075720'),
('20200505103316'); ('20200421093637'),
('20200505103316'),
('20200505150413'),
('20200518104105');

View file

@ -35,6 +35,11 @@ namespace :whois do
ReservedDomain.find_in_batches.each do |group| ReservedDomain.find_in_batches.each do |group|
UpdateWhoisRecordJob.enqueue group.map(&:name), 'reserved' UpdateWhoisRecordJob.enqueue group.map(&:name), 'reserved'
end end
print "\n-----> Update disputed domains whois_records"
Dispute.active.find_in_batches.each do |group|
UpdateWhoisRecordJob.enqueue group.map(&:domain_name), 'disputed'
end
end end
puts "\n-----> all done in #{(Time.zone.now.to_f - start).round(2)} seconds" puts "\n-----> all done in #{(Time.zone.now.to_f - start).round(2)} seconds"
end end

22
test/fixtures/disputes.yml vendored Normal file
View file

@ -0,0 +1,22 @@
active:
domain_name: active-dispute.test
password: active-001
starts_at: <%= Date.parse '2010-07-05' %>
expires_at: <%= Date.parse '2013-07-05' %>
future:
domain_name: future-dispute.test
password: active-001
starts_at: <%= Date.parse '2010-10-05' %>
expires_at: <%= Date.parse '2013-10-05' %>
expired:
domain_name: expired-dispute.test
password: active-001
starts_at: <%= Date.parse '2010-07-05' %>
expires_at: <%= Date.parse '2013-07-05' %>
closed: <%= Date.parse '2013-07-05' %>
closed:
domain_name: closed_dispute.test
password: active-001
starts_at: <%= Date.parse '2010-07-05' %>
expires_at: <%= Date.parse '2013-07-05' %>
closed: <%= Date.parse '2013-07-05' %>

View file

@ -0,0 +1,91 @@
require 'application_system_test_case'
require 'test_helper'
class AdminDisputesSystemTest < ApplicationSystemTestCase
include ActionView::Helpers::NumberHelper
setup do
@dispute = disputes(:active)
@original_default_language = Setting.default_language
sign_in users(:admin)
end
teardown do
Setting.default_language = @original_default_language
end
def test_creates_new_dispute
assert_nil Dispute.active.find_by(domain_name: 'hospital.test')
visit admin_disputes_path
click_on 'New disputed domain'
fill_in 'Domain name', with: 'hospital.test'
fill_in 'Password', with: '1234'
fill_in 'Starts at', with: (Time.zone.today - 2.years).to_s
fill_in 'Comment', with: 'Sample comment'
click_on 'Save'
assert_text 'Dispute was successfully created.'
assert_text 'hospital.test'
end
def test_creates_new_dispute_for_unregistered_domain
assert_nil Dispute.active.find_by(domain_name: 'nonexistant.test')
visit admin_disputes_path
click_on 'New disputed domain'
fill_in 'Domain name', with: 'nonexistant.test'
fill_in 'Password', with: '1234'
fill_in 'Starts at', with: Time.zone.today.to_s
fill_in 'Comment', with: 'Sample comment'
click_on 'Save'
assert_text 'Dispute was successfully created for domain that is not registered.'
assert_text 'nonexistant.test'
end
def test_throws_error_if_starts_at_is_in_future
assert_nil Dispute.active.find_by(domain_name: 'disputed.test')
visit admin_disputes_path
click_on 'New disputed domain'
fill_in 'Domain name', with: 'disputed.test'
fill_in 'Password', with: '1234'
fill_in 'Starts at', with: (Time.zone.today + 2.day).to_s
fill_in 'Comment', with: 'Sample comment'
click_on 'Save'
assert_text "Can not be greater than today's date"
end
def test_updates_dispute
assert_not_equal Time.zone.today, @dispute.starts_at
visit edit_admin_dispute_path(@dispute)
fill_in 'Starts at', with: Time.zone.today.to_s
click_link_or_button 'Save'
assert_text 'Dispute was successfully updated'
assert_text Time.zone.today
end
def test_deletes_dispute
visit delete_admin_dispute_path(@dispute)
assert_text 'Dispute was successfully closed.'
end
def test_can_not_create_overlapping_dispute
visit admin_disputes_path
click_on 'New disputed domain'
fill_in 'Domain name', with: 'active-dispute.test'
fill_in 'Starts at', with: @dispute.starts_at + 1.day
click_on 'Save'
assert_text 'Dispute already exists for this domain at given timeframe'
end
end

View file

@ -0,0 +1,70 @@
require "test_helper"
class DisputeStatusUpdateJobTest < ActiveSupport::TestCase
setup do
travel_to Time.zone.parse('2010-10-05')
@logger = Rails.logger
end
def test_nothing_is_raised
assert_nothing_raised do
DisputeStatusUpdateJob.run(logger: @logger)
end
end
def test_whois_data_added_when_dispute_activated
dispute = disputes(:future)
DisputeStatusUpdateJob.run(logger: @logger)
whois_record = Whois::Record.find_by(name: dispute.domain_name)
assert whois_record.present?
assert_includes whois_record.json['status'], 'disputed'
end
def test_on_expiry_unregistered_domain_is_sent_to_auction
dispute = disputes(:active)
dispute.update!(starts_at: Time.zone.today - 3.years - 1.day)
DisputeStatusUpdateJob.run(logger: @logger)
dispute.reload
assert dispute.closed
whois_record = Whois::Record.find_by(name: dispute.domain_name)
assert_equal ['AtAuction'], whois_record.json['status']
end
def test_registered_domain_whois_data_is_added
Dispute.create(domain_name: 'shop.test', starts_at: '2010-07-05')
travel_to Time.zone.parse('2010-07-05')
DisputeStatusUpdateJob.run(logger: @logger)
whois_record = Whois::Record.find_by(name: 'shop.test')
assert_includes whois_record.json['status'], 'disputed'
end
def test_registered_domain_whois_data_is_removed
travel_to Time.zone.parse('2010-07-05')
domain = domains(:shop)
domain.update(valid_to: Time.zone.parse('2015-07-05').to_s(:db),
outzone_at: Time.zone.parse('2015-07-06').to_s(:db),
delete_date: nil,
force_delete_date: nil)
# Dispute status is added automatically if starts_at is not in future
Dispute.create(domain_name: 'shop.test', starts_at: Time.zone.parse('2010-07-05'))
domain.reload
whois_record = Whois::Record.find_by(name: 'shop.test')
assert_includes whois_record.json['status'], 'disputed'
# Dispute status is removed night time day after it's ended
travel_to Time.zone.parse('2010-07-05') + 3.years + 1.day
DisputeStatusUpdateJob.run(logger: @logger)
whois_record.reload
assert_not whois_record.json['status'].include? 'disputed'
end
end

View file

@ -0,0 +1,52 @@
require 'test_helper'
class DisputedDomainTest < ActiveSupport::TestCase
setup do
@dispute = disputes(:active)
end
def test_fixture_is_valid
assert @dispute.valid?
end
def test_can_be_closed_by_domain_name
travel_to Time.zone.parse('2010-10-05')
Dispute.close_by_domain(@dispute.domain_name)
@dispute.reload
assert @dispute.closed
end
def test_syncs_password_to_reserved
dispute = Dispute.new(domain_name: 'reserved.test', starts_at: Time.zone.today, password: 'disputepw')
dispute.save
dispute.reload
assert_equal dispute.password, ReservedDomain.find_by(name: dispute.domain_name).password
end
def test_domain_name_zone_is_validated
dispute = Dispute.new(domain_name: 'correct.test', starts_at: Time.zone.today)
assert dispute.valid?
dispute.domain_name = 'zone.is.unrecognized.test'
assert_not dispute.valid?
end
def test_dispute_can_not_be_created_if_another_active_is_present
dispute = Dispute.new(domain_name: @dispute.domain_name,
starts_at: @dispute.starts_at + 1.day)
assert_not dispute.valid?
end
def test_expires_at_date_is_appended_automatically
dispute = Dispute.new(domain_name: 'random.test', starts_at: Time.zone.today)
assert dispute.valid?
assert_equal dispute.expires_at, dispute.starts_at + 3.years
end
def test_starts_at_must_be_present
dispute = Dispute.new(domain_name: 'random.test')
assert_not dispute.valid?
end
end

View file

@ -1,6 +1,6 @@
require 'application_system_test_case' require 'application_system_test_case'
class BankStatementTest < ApplicationSystemTestCase class AdminBankStatementsSystemTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
travel_to Time.zone.parse('2010-07-05 00:30:00') travel_to Time.zone.parse('2010-07-05 00:30:00')