mirror of
https://github.com/internetee/registry.git
synced 2025-06-11 23:24:48 +02:00
Merge pull request #1574 from internetee/269-dispute-list
Implement disputed logic for domains
This commit is contained in:
commit
9308ed7b91
40 changed files with 1073 additions and 76 deletions
74
app/controllers/admin/disputes_controller.rb
Normal file
74
app/controllers/admin/disputes_controller.rb
Normal 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
|
|
@ -92,7 +92,7 @@ module Epp
|
|||
status: Auction.statuses[:payment_received])
|
||||
active_auction.domain_registered!
|
||||
end
|
||||
|
||||
Dispute.close_by_domain(@domain.name)
|
||||
render_epp_response '/epp/domains/create'
|
||||
else
|
||||
handle_errors(@domain)
|
||||
|
@ -103,21 +103,17 @@ module Epp
|
|||
def update
|
||||
authorize! :update, @domain, @password
|
||||
|
||||
if @domain.update(params[:parsed_frame], current_user)
|
||||
if @domain.epp_pending_update.present?
|
||||
render_epp_response '/epp/domains/success_pending'
|
||||
else
|
||||
render_epp_response '/epp/domains/success'
|
||||
end
|
||||
else
|
||||
handle_errors(@domain)
|
||||
end
|
||||
updated = @domain.update(params[:parsed_frame], current_user)
|
||||
(handle_errors(@domain) && return) unless updated
|
||||
|
||||
pending = @domain.epp_pending_update.present?
|
||||
render_epp_response "/epp/domains/success#{'_pending' if pending}"
|
||||
end
|
||||
|
||||
def delete
|
||||
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_pending_delete.present?
|
||||
|
|
|
@ -4,6 +4,7 @@ class Registrant::DomainDeleteConfirmsController < RegistrantController
|
|||
|
||||
def show
|
||||
return if params[:confirmed] || params[:rejected]
|
||||
|
||||
@domain = Domain.find(params[:id])
|
||||
@domain = nil unless @domain.registrant_delete_confirmable?(params[:token])
|
||||
end
|
||||
|
@ -21,22 +22,23 @@ class Registrant::DomainDeleteConfirmsController < RegistrantController
|
|||
initiator = current_registrant_user ? current_registrant_user.username :
|
||||
t(:user_not_authenticated)
|
||||
|
||||
if params[:rejected]
|
||||
if @registrant_verification.domain_registrant_delete_reject!("email link #{initiator}")
|
||||
flash[:notice] = t(:registrant_domain_verification_rejected)
|
||||
redirect_to registrant_domain_delete_confirm_path(@domain.id, rejected: true)
|
||||
else
|
||||
flash[:alert] = t(:registrant_domain_delete_rejected_failed)
|
||||
return render 'show'
|
||||
end
|
||||
elsif params[:confirmed]
|
||||
if @registrant_verification.domain_registrant_delete_confirm!("email link #{initiator}")
|
||||
flash[:notice] = t(:registrant_domain_verification_confirmed)
|
||||
redirect_to registrant_domain_delete_confirm_path(@domain.id, confirmed: true)
|
||||
else
|
||||
flash[:alert] = t(:registrant_domain_delete_confirmed_failed)
|
||||
return render 'show'
|
||||
end
|
||||
confirmed = params[:confirmed] ? true : false
|
||||
action = if confirmed
|
||||
@registrant_verification.domain_registrant_delete_reject!("email link #{initiator}")
|
||||
else
|
||||
@registrant_verification.domain_registrant_delete_confirm!("email link #{initiator}")
|
||||
end
|
||||
|
||||
fail_msg = t("registrant_domain_delete_#{confirmed ? 'confirmed' : 'rejected'}_failed".to_sym)
|
||||
success_msg = t("registrant_domain_verification_#{confirmed ? 'confirmed' : 'rejected'}".to_sym)
|
||||
|
||||
flash[:alert] = action ? success_msg : fail_msg
|
||||
(render 'show' && return) unless action
|
||||
|
||||
if confirmed
|
||||
redirect_to registrant_domain_delete_confirm_path(@domain.id, confirmed: true) && return
|
||||
else
|
||||
redirect_to registrant_domain_delete_confirm_path(@domain.id, rejected: true) unless confirmed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,8 @@ class Registrant::DomainUpdateConfirmsController < RegistrantController
|
|||
end
|
||||
elsif params[:confirmed]
|
||||
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)
|
||||
redirect_to registrant_domain_update_confirm_path(@domain.id, confirmed: true)
|
||||
else
|
||||
|
|
|
@ -100,12 +100,14 @@ class Registrar
|
|||
authorize! :update, Depp::Domain
|
||||
@data = @domain.info(params[:domain_name])
|
||||
@domain_params = Depp::Domain.construct_params_from_server_data(@data)
|
||||
@dispute = Dispute.active.find_by(domain_name: params[:domain_name])
|
||||
end
|
||||
|
||||
def update
|
||||
authorize! :update, Depp::Domain
|
||||
@domain_params = params[:domain]
|
||||
@data = @domain.update(@domain_params)
|
||||
@dispute = Dispute.active.find_by(domain_name: @domain_params[:name])
|
||||
|
||||
if response_ok?
|
||||
redirect_to info_registrar_domains_url(domain_name: @domain_params[:name])
|
||||
|
|
63
app/jobs/dispute_status_update_job.rb
Normal file
63
app/jobs/dispute_status_update_job.rb
Normal 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
|
|
@ -1,13 +1,14 @@
|
|||
class UpdateWhoisRecordJob < Que::Job
|
||||
|
||||
def run(names, type)
|
||||
::PaperTrail.whodunnit = "job - #{self.class.name} - #{type}"
|
||||
::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{type}"
|
||||
|
||||
klass = case type
|
||||
when 'reserved'then ReservedDomain
|
||||
when 'blocked' then BlockedDomain
|
||||
when 'domain' then Domain
|
||||
end
|
||||
when 'reserved' then ReservedDomain
|
||||
when 'blocked' then BlockedDomain
|
||||
when 'domain' then Domain
|
||||
when 'disputed' then Dispute.active
|
||||
end
|
||||
|
||||
Array(names).each do |name|
|
||||
record = klass.find_by(name: name)
|
||||
|
@ -19,8 +20,6 @@ class UpdateWhoisRecordJob < Que::Job
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def update_domain(domain)
|
||||
domain.whois_record ? domain.whois_record.save : domain.create_whois_record
|
||||
end
|
||||
|
@ -33,6 +32,9 @@ class UpdateWhoisRecordJob < Que::Job
|
|||
update_reserved(record)
|
||||
end
|
||||
|
||||
def update_disputed(record)
|
||||
update_reserved(record)
|
||||
end
|
||||
|
||||
# 1. deleting own
|
||||
# 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)
|
||||
ReservedDomain.find_by(name: name).try(:generate_data)
|
||||
Dispute.active.find_by(domain_name: name).try(:generate_data)
|
||||
end
|
||||
|
||||
def delete_reserved(name)
|
||||
Domain.where(name: name).any?
|
||||
Whois::Record.where(name: name).delete_all
|
||||
remove_status_from_whois(domain_name: name, domain_status: 'Reserved')
|
||||
end
|
||||
|
||||
def delete_blocked(name)
|
||||
delete_reserved(name)
|
||||
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
|
||||
|
|
|
@ -100,6 +100,7 @@ class Ability
|
|||
can :manage, Invoice
|
||||
can :manage, WhiteIp
|
||||
can :manage, AccountActivity
|
||||
can :manage, Dispute
|
||||
can :read, ApiLog::EppLog
|
||||
can :read, ApiLog::ReppLog
|
||||
can :update, :pending
|
||||
|
|
44
app/models/concerns/domain/disputable.rb
Normal file
44
app/models/concerns/domain/disputable.rb
Normal 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
|
15
app/models/concerns/whois_status_populate.rb
Normal file
15
app/models/concerns/whois_status_populate.rb
Normal 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
133
app/models/dispute.rb
Normal 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
|
|
@ -68,6 +68,10 @@ module DNS
|
|||
ReservedDomain.where(name: name).any?
|
||||
end
|
||||
|
||||
def disputed?
|
||||
Dispute.active.where(domain_name: name).any?
|
||||
end
|
||||
|
||||
def auctionable?
|
||||
!not_auctionable?
|
||||
end
|
||||
|
@ -81,7 +85,7 @@ module DNS
|
|||
attr_reader :name
|
||||
|
||||
def not_auctionable?
|
||||
blocked? || reserved?
|
||||
blocked? || reserved? || disputed?
|
||||
end
|
||||
|
||||
def zone_with_same_origin?
|
||||
|
|
|
@ -9,6 +9,7 @@ class Domain < ApplicationRecord
|
|||
include Concerns::Domain::Transferable
|
||||
include Concerns::Domain::RegistryLockable
|
||||
include Concerns::Domain::Releasable
|
||||
include Concerns::Domain::Disputable
|
||||
|
||||
attr_accessor :roles
|
||||
|
||||
|
@ -88,8 +89,8 @@ class Domain < ApplicationRecord
|
|||
validates :puny_label, length: { maximum: 63 }
|
||||
validates :period, presence: true, numericality: { only_integer: true }
|
||||
validates :transfer_code, presence: true
|
||||
|
||||
validate :validate_reservation
|
||||
|
||||
def validate_reservation
|
||||
return if persisted? || !in_reserved_list?
|
||||
|
||||
|
@ -99,6 +100,7 @@ class Domain < ApplicationRecord
|
|||
end
|
||||
|
||||
return if ReservedDomain.pw_for(name) == reserved_pw
|
||||
|
||||
errors.add(:base, :invalid_auth_information_reserved)
|
||||
end
|
||||
|
||||
|
@ -282,20 +284,23 @@ class Domain < ApplicationRecord
|
|||
def server_holdable?
|
||||
return false if statuses.include?(DomainStatus::SERVER_HOLD)
|
||||
return false if statuses.include?(DomainStatus::SERVER_MANUAL_INZONE)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def renewable?
|
||||
if 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 - Date.today) + 1 > Setting.days_to_renew_domain_before_expire
|
||||
return false
|
||||
end
|
||||
blocking_statuses = [DomainStatus::DELETE_CANDIDATE, DomainStatus::PENDING_RENEW,
|
||||
DomainStatus::PENDING_TRANSFER, DomainStatus::DISPUTED,
|
||||
DomainStatus::PENDING_UPDATE, DomainStatus::PENDING_DELETE,
|
||||
DomainStatus::PENDING_DELETE_CONFIRMATION]
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DomainStatus < ApplicationRecord
|
||||
include EppErrors
|
||||
belongs_to :domain
|
||||
|
@ -70,6 +72,7 @@ class DomainStatus < ApplicationRecord
|
|||
FORCE_DELETE = 'serverForceDelete'
|
||||
DELETE_CANDIDATE = 'deleteCandidate'
|
||||
EXPIRED = 'expired'
|
||||
DISPUTED = 'disputed'
|
||||
|
||||
STATUSES = [
|
||||
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,
|
||||
PENDING_UPDATE, SERVER_MANUAL_INZONE, SERVER_REGISTRANT_CHANGE_PROHIBITED,
|
||||
SERVER_ADMIN_CHANGE_PROHIBITED, SERVER_TECH_CHANGE_PROHIBITED, FORCE_DELETE,
|
||||
DELETE_CANDIDATE, EXPIRED
|
||||
]
|
||||
DELETE_CANDIDATE, EXPIRED, DISPUTED
|
||||
].freeze
|
||||
|
||||
CLIENT_STATUSES = [
|
||||
CLIENT_DELETE_PROHIBITED, CLIENT_HOLD, CLIENT_RENEW_PROHIBITED, CLIENT_TRANSFER_PROHIBITED,
|
||||
CLIENT_UPDATE_PROHIBITED
|
||||
]
|
||||
].freeze
|
||||
|
||||
SERVER_STATUSES = [
|
||||
SERVER_DELETE_PROHIBITED, SERVER_HOLD, SERVER_RENEW_PROHIBITED, SERVER_TRANSFER_PROHIBITED,
|
||||
SERVER_UPDATE_PROHIBITED, SERVER_MANUAL_INZONE, SERVER_REGISTRANT_CHANGE_PROHIBITED,
|
||||
SERVER_ADMIN_CHANGE_PROHIBITED, SERVER_TECH_CHANGE_PROHIBITED
|
||||
]
|
||||
].freeze
|
||||
|
||||
UPDATE_PROHIBIT_STATES = [
|
||||
DomainStatus::PENDING_DELETE_CONFIRMATION,
|
||||
|
|
|
@ -53,12 +53,13 @@ class Epp::Domain < Domain
|
|||
def epp_code_map
|
||||
{
|
||||
'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
|
||||
[:registrant, :blank],
|
||||
[:registrar, :blank],
|
||||
[:base, :required_parameter_missing_reserved]
|
||||
%i[registrant blank],
|
||||
%i[registrar blank],
|
||||
%i[base required_parameter_missing_reserved],
|
||||
%i[base required_parameter_missing_disputed],
|
||||
],
|
||||
'2004' => [ # Parameter value range error
|
||||
[:dnskeys, :out_of_range,
|
||||
|
@ -85,10 +86,11 @@ class Epp::Domain < Domain
|
|||
[:puny_label, :too_long, { obj: 'name', val: name_puny }]
|
||||
],
|
||||
'2201' => [ # Authorisation error
|
||||
[:transfer_code, :wrong_pw]
|
||||
%i[transfer_code wrong_pw],
|
||||
],
|
||||
'2202' => [
|
||||
[:base, :invalid_auth_information_reserved]
|
||||
%i[base invalid_auth_information_reserved],
|
||||
%i[base invalid_auth_information_disputed],
|
||||
],
|
||||
'2302' => [ # Object exists
|
||||
[:name_dirty, :taken, { value: { obj: 'name', val: name_dirty } }],
|
||||
|
@ -473,13 +475,35 @@ class Epp::Domain < Domain
|
|||
self.up_date = Time.zone.now
|
||||
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 &&
|
||||
Setting.request_confrimation_on_registrant_change_enabled &&
|
||||
frame.css('registrant').present? &&
|
||||
frame.css('registrant').attr('verified').to_s.downcase != 'yes'
|
||||
registrant_verification_asked!(frame.to_s, current_user.id)
|
||||
unverified_registrant_params
|
||||
registrant_verification_asked!(frame.to_s, current_user.id) unless disputed?
|
||||
end
|
||||
|
||||
errors.empty? && super(at)
|
||||
|
@ -706,6 +730,11 @@ class Epp::Domain < Domain
|
|||
|
||||
|
||||
def can_be_deleted?
|
||||
if disputed?
|
||||
errors.add(:base, :domain_status_prohibits_operation)
|
||||
return false
|
||||
end
|
||||
|
||||
begin
|
||||
errors.add(:base, :domain_status_prohibits_operation)
|
||||
return false
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
class ReservedDomain < ApplicationRecord
|
||||
include Versions # version/reserved_domain_version.rb
|
||||
include WhoisStatusPopulate
|
||||
before_save :fill_empty_passwords
|
||||
before_save :generate_data
|
||||
before_save :sync_dispute_password
|
||||
after_destroy :remove_data
|
||||
|
||||
validates :name, domain_name: true, uniqueness: true
|
||||
|
@ -41,23 +43,21 @@ class ReservedDomain < ApplicationRecord
|
|||
self.password = SecureRandom.hex
|
||||
end
|
||||
|
||||
def sync_dispute_password
|
||||
dispute = Dispute.active.find_by(domain_name: name)
|
||||
self.password = dispute.password if dispute.present?
|
||||
end
|
||||
|
||||
def generate_data
|
||||
return if Domain.where(name: name).any?
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
UpdateWhoisRecordJob.enqueue name, 'reserved'
|
||||
end
|
||||
|
|
|
@ -46,6 +46,7 @@ class Setting < RailsSettings::Base
|
|||
expire_warning_period
|
||||
redemption_grace_period
|
||||
expire_pending_confirmation
|
||||
dispute_period_in_months
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ class WhoisRecord < ApplicationRecord
|
|||
|
||||
def populate
|
||||
return if domain_id.blank?
|
||||
|
||||
self.json = generated_json
|
||||
self.name = json['name']
|
||||
self.registrar_id = domain.registrar_id if domain # for faster registrar updates
|
||||
|
|
|
@ -32,10 +32,11 @@
|
|||
%li= link_to t('.zones'), admin_zones_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('.disputed_domains'), admin_disputes_path
|
||||
%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('.que'), '/admin/que'
|
||||
|
||||
%ul.nav.navbar-nav.navbar-right
|
||||
%li= link_to t('.sign_out'), destroy_admin_user_session_path, method: :delete,
|
||||
class: 'navbar-link'
|
||||
class: 'navbar-link'
|
||||
|
|
60
app/views/admin/disputes/_form.html.erb
Normal file
60
app/views/admin/disputes/_form.html.erb
Normal 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 %>
|
3
app/views/admin/disputes/edit.haml
Normal file
3
app/views/admin/disputes/edit.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
= render 'shared/title', name: t(:edit_dispute)
|
||||
|
||||
= render 'form'
|
175
app/views/admin/disputes/index.html.erb
Normal file
175
app/views/admin/disputes/index.html.erb
Normal 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">
|
||||
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
|
||||
</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>
|
3
app/views/admin/disputes/new.haml
Normal file
3
app/views/admin/disputes/new.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
= render 'shared/title', name: t(:add_disputed_domain)
|
||||
|
||||
= render 'form'
|
|
@ -48,7 +48,7 @@
|
|||
= render 'setting_row', var: :request_confrimation_on_registrant_change_enabled
|
||||
= render 'setting_row', var: :request_confirmation_on_domain_deletion_enabled
|
||||
= render 'setting_row', var: :address_processing
|
||||
|
||||
= render 'setting_row', var: :dispute_period_in_months
|
||||
%tr
|
||||
%td.col-md-6= label_tag :default_language
|
||||
%td.col-md-6
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
.col-md-7
|
||||
= 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
|
||||
.col-md-3.control-label
|
||||
= label_tag :domain_reserved_pw, t(:reserved_pw)
|
||||
|
|
|
@ -44,6 +44,8 @@ defaults: &defaults
|
|||
registrar_ip_whitelist_enabled: false
|
||||
api_ip_whitelist_enabled: false
|
||||
|
||||
dispute_period_in_months: 36
|
||||
|
||||
registry_juridical_name: "Eesti Interneti SA"
|
||||
registry_reg_no: "90010019"
|
||||
registry_email: "info@internet.ee"
|
||||
|
|
19
config/locales/admin/disputes.en.yml
Normal file
19
config/locales/admin/disputes.en.yml
Normal 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
|
|
@ -13,6 +13,7 @@ en:
|
|||
zones: Zones
|
||||
blocked_domains: Blocked domains
|
||||
reserved_domains: Reserved domains
|
||||
disputed_domains: Disputed domains
|
||||
epp_log: EPP log
|
||||
repp_log: REPP log
|
||||
que: Que
|
||||
|
|
|
@ -24,6 +24,8 @@ en:
|
|||
key_data_not_allowed: 'keyData object is not allowed'
|
||||
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'
|
||||
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]'
|
||||
name_dirty:
|
||||
invalid: 'Domain name is invalid'
|
||||
|
@ -629,7 +631,9 @@ en:
|
|||
available_verification_url_not_found: 'Available verification url not found, for domain.'
|
||||
add_reserved_domain: 'Add domain to reserved list'
|
||||
add_blocked_domain: 'Add domain to blocked list'
|
||||
add_disputed_domain: 'Add domain to disputed list'
|
||||
edit_pw: 'Edit Pw'
|
||||
edit_dispute: 'Edit dispute'
|
||||
optional: 'Optional'
|
||||
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?'
|
||||
|
|
|
@ -271,6 +271,11 @@ Rails.application.routes.draw do
|
|||
get 'delete'
|
||||
end
|
||||
end
|
||||
resources :disputes do
|
||||
member do
|
||||
get 'delete'
|
||||
end
|
||||
end
|
||||
|
||||
resources :registrars do
|
||||
resources :api_users, except: %i[index]
|
||||
|
|
14
db/migrate/20200421093637_create_disputes.rb
Normal file
14
db/migrate/20200421093637_create_disputes.rb
Normal 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
|
|
@ -0,0 +1,5 @@
|
|||
class AddDisputePeriodInMonthsToSetting < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
Setting.create(var: 'dispute_period_in_months', value: 36)
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
---
|
||||
--- PostgreSQL database dump
|
||||
---
|
||||
|
||||
SET statement_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;
|
||||
|
||||
|
||||
--
|
||||
-- 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:
|
||||
--
|
||||
|
@ -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);
|
||||
|
||||
|
||||
--
|
||||
-- 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: -
|
||||
--
|
||||
|
@ -2811,6 +2855,14 @@ ALTER TABLE ONLY public.directos
|
|||
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:
|
||||
--
|
||||
|
@ -4467,5 +4519,9 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20200204103125'),
|
||||
('20200311114649'),
|
||||
('20200417075720'),
|
||||
('20200505103316');
|
||||
('20200421093637'),
|
||||
('20200505103316'),
|
||||
('20200505150413'),
|
||||
('20200518104105');
|
||||
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ namespace :whois do
|
|||
ReservedDomain.find_in_batches.each do |group|
|
||||
UpdateWhoisRecordJob.enqueue group.map(&:name), 'reserved'
|
||||
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
|
||||
puts "\n-----> all done in #{(Time.zone.now.to_f - start).round(2)} seconds"
|
||||
end
|
||||
|
|
22
test/fixtures/disputes.yml
vendored
Normal file
22
test/fixtures/disputes.yml
vendored
Normal 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' %>
|
91
test/integration/admin_area/disputes_test.rb
Normal file
91
test/integration/admin_area/disputes_test.rb
Normal 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
|
70
test/jobs/dispute_status_update_job_test.rb
Normal file
70
test/jobs/dispute_status_update_job_test.rb
Normal 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
|
52
test/models/disputed_domain_test.rb
Normal file
52
test/models/disputed_domain_test.rb
Normal 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
|
|
@ -1,6 +1,6 @@
|
|||
require 'application_system_test_case'
|
||||
|
||||
class BankStatementTest < ApplicationSystemTestCase
|
||||
class AdminBankStatementsSystemTest < ApplicationSystemTestCase
|
||||
setup do
|
||||
sign_in users(:admin)
|
||||
travel_to Time.zone.parse('2010-07-05 00:30:00')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue