diff --git a/app/controllers/admin/disputes_controller.rb b/app/controllers/admin/disputes_controller.rb new file mode 100644 index 000000000..8a8997f63 --- /dev/null +++ b/app/controllers/admin/disputes_controller.rb @@ -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 diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb index 9e4f6123a..fa0003756 100644 --- a/app/controllers/epp/domains_controller.rb +++ b/app/controllers/epp/domains_controller.rb @@ -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? diff --git a/app/controllers/registrant/domain_delete_confirms_controller.rb b/app/controllers/registrant/domain_delete_confirms_controller.rb index 95eefc368..ba5dd2ba7 100644 --- a/app/controllers/registrant/domain_delete_confirms_controller.rb +++ b/app/controllers/registrant/domain_delete_confirms_controller.rb @@ -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 diff --git a/app/controllers/registrant/domain_update_confirms_controller.rb b/app/controllers/registrant/domain_update_confirms_controller.rb index 61e623ddf..0e4f2a582 100644 --- a/app/controllers/registrant/domain_update_confirms_controller.rb +++ b/app/controllers/registrant/domain_update_confirms_controller.rb @@ -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 diff --git a/app/controllers/registrar/domains_controller.rb b/app/controllers/registrar/domains_controller.rb index e6e1029f6..50ad0bd10 100644 --- a/app/controllers/registrar/domains_controller.rb +++ b/app/controllers/registrar/domains_controller.rb @@ -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]) diff --git a/app/jobs/dispute_status_update_job.rb b/app/jobs/dispute_status_update_job.rb new file mode 100644 index 000000000..547d56868 --- /dev/null +++ b/app/jobs/dispute_status_update_job.rb @@ -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 diff --git a/app/jobs/update_whois_record_job.rb b/app/jobs/update_whois_record_job.rb index bee0e032c..16f4e0e79 100644 --- a/app/jobs/update_whois_record_job.rb +++ b/app/jobs/update_whois_record_job.rb @@ -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 diff --git a/app/models/ability.rb b/app/models/ability.rb index a727254ad..0e18f433a 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -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 diff --git a/app/models/concerns/domain/disputable.rb b/app/models/concerns/domain/disputable.rb new file mode 100644 index 000000000..a05d7cea6 --- /dev/null +++ b/app/models/concerns/domain/disputable.rb @@ -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 diff --git a/app/models/concerns/whois_status_populate.rb b/app/models/concerns/whois_status_populate.rb new file mode 100644 index 000000000..616cc7d22 --- /dev/null +++ b/app/models/concerns/whois_status_populate.rb @@ -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 diff --git a/app/models/dispute.rb b/app/models/dispute.rb new file mode 100644 index 000000000..45ff27274 --- /dev/null +++ b/app/models/dispute.rb @@ -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 diff --git a/app/models/dns/domain_name.rb b/app/models/dns/domain_name.rb index e4dd24fa5..c1af4d5e7 100644 --- a/app/models/dns/domain_name.rb +++ b/app/models/dns/domain_name.rb @@ -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? diff --git a/app/models/domain.rb b/app/models/domain.rb index f21317b70..fff0d4a08 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -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 diff --git a/app/models/domain_status.rb b/app/models/domain_status.rb index 4b1c49916..bf0ae2a51 100644 --- a/app/models/domain_status.rb +++ b/app/models/domain_status.rb @@ -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, diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb index dc80b2e40..c50203dd9 100644 --- a/app/models/epp/domain.rb +++ b/app/models/epp/domain.rb @@ -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 diff --git a/app/models/reserved_domain.rb b/app/models/reserved_domain.rb index 11c9bb2f5..4c9df3269 100644 --- a/app/models/reserved_domain.rb +++ b/app/models/reserved_domain.rb @@ -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 diff --git a/app/models/setting.rb b/app/models/setting.rb index 64a20c34f..9f00055a3 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -46,6 +46,7 @@ class Setting < RailsSettings::Base expire_warning_period redemption_grace_period expire_pending_confirmation + dispute_period_in_months ] end diff --git a/app/models/whois_record.rb b/app/models/whois_record.rb index cace829fa..4994283c9 100644 --- a/app/models/whois_record.rb +++ b/app/models/whois_record.rb @@ -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 diff --git a/app/views/admin/base/_menu.haml b/app/views/admin/base/_menu.haml index fa1b50440..a327419fd 100644 --- a/app/views/admin/base/_menu.haml +++ b/app/views/admin/base/_menu.haml @@ -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' \ No newline at end of file + class: 'navbar-link' diff --git a/app/views/admin/disputes/_form.html.erb b/app/views/admin/disputes/_form.html.erb new file mode 100644 index 000000000..2a3fb722f --- /dev/null +++ b/app/views/admin/disputes/_form.html.erb @@ -0,0 +1,60 @@ +<%= form_for([:admin, @dispute], html: { class: 'form-horizontal' }) do |f| %> + <%= render 'shared/full_errors', object: @dispute %> + +
+
+
+
+
+ <%= t(:general) %> +
+
+
+
+

As per domain law, expiry time is <%= Setting.dispute_period_in_months / 12 %> years ahead from start date.

+
+
+
+ <%= f.label :domain_name %> +
+
+ <%= f.text_field(:domain_name, class: 'form-control', disabled: !f.object.new_record?) %> +
+
+
+
+ <%= f.label :password %> +
+
+ <%= f.text_field(:password, placeholder: t(:optional), class: 'form-control') %> + <%= t '.password_hint' %> +
+
+
+
+ <%= f.label :starts_at %> +
+
+ <%= f.text_field(:starts_at, class: 'form-control js-datepicker') %> + <%= t '.past_or_today' %> +
+
+
+
+ <%= f.label :comment %> +
+
+ <%= f.text_field(:comment, placeholder: t(:optional), class: 'form-control') %> +
+
+
+
+
+
+ +
+
+ <%= button_tag(t(:save), class: 'btn btn-primary') %> +
+
+<% end %> diff --git a/app/views/admin/disputes/edit.haml b/app/views/admin/disputes/edit.haml new file mode 100644 index 000000000..966976891 --- /dev/null +++ b/app/views/admin/disputes/edit.haml @@ -0,0 +1,3 @@ += render 'shared/title', name: t(:edit_dispute) + += render 'form' diff --git a/app/views/admin/disputes/index.html.erb b/app/views/admin/disputes/index.html.erb new file mode 100644 index 000000000..e32ddb730 --- /dev/null +++ b/app/views/admin/disputes/index.html.erb @@ -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') %> +
+
+ <%= search_form_for [:admin, @q], html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f| %> +
+
+
+ <%= f.label :domain_name %> + <%= f.search_field :domain_name_matches, value: params[:q][:domain_name_matches], class: 'form-control', placeholder: t(:name) %> +
+
+
+
+ <%= 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) %> +
+
+
+
+ <%= 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) %> +
+
+
+
+
+
+ <%= label_tag t(:results_per_page) %> + <%= text_field_tag :results_per_page, params[:results_per_page], class: 'form-control', placeholder: t(:results_per_page) %> +
+
+
+ + <%= link_to(t('.reset_btn'), admin_disputes_path, class: 'btn btn-default') %> +
+
+ <% end %> +
+
+
+

Active disputes

+
+
+
+ + + + + + + + + + + + + <% @disputes.each do |x| %> + + + + + + + + + <% end %> + +
+ <%= sort_link(@q, 'domain_name') %> + + <%= sort_link(@q, 'password') %> + + <%= sort_link(@q, 'starts_at') %> + + <%= sort_link(@q, 'expires_at') %> + + <%= sort_link(@q, 'comment') %> + + <%= t(:actions) %> +
+ <%= x.domain_name %> + + <%= x.password %> + + <%= x.starts_at %> + + <%= x.expires_at %> + + <%= x.comment %> + + <%= 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' %> +
+
+
+
+
+
+ <%= paginate @disputes %> +
+
+ +
+
+
+

Expired / Closed disputes

+
+
+
+ + + + + + + + + + + + <% @closed_disputes.each do |x| %> + + + + + + + + <% end %> + +
+ <%= sort_link(@q, 'domain_name') %> + + <%= sort_link(@q, 'initiator') %> + + <%= sort_link(@q, 'starts_at') %> + + <%= sort_link(@q, 'closed') %> + + <%= sort_link(@q, 'comment') %> +
+ <%= x.domain_name %> + + <%= x.initiator %> + + <%= x.starts_at %> + + <%= x.closed %> + + <%= x.comment %> +
+
+
+
+
+
+ <%= paginate @closed_disputes, param_name: :closed_page %> +
+
+ +
+
diff --git a/app/views/admin/disputes/new.haml b/app/views/admin/disputes/new.haml new file mode 100644 index 000000000..0a57af7be --- /dev/null +++ b/app/views/admin/disputes/new.haml @@ -0,0 +1,3 @@ += render 'shared/title', name: t(:add_disputed_domain) + += render 'form' diff --git a/app/views/admin/settings/index.haml b/app/views/admin/settings/index.haml index 23f87c4b2..977f81202 100644 --- a/app/views/admin/settings/index.haml +++ b/app/views/admin/settings/index.haml @@ -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 diff --git a/app/views/registrar/domains/form/_general.haml b/app/views/registrar/domains/form/_general.haml index 0a729a262..5fa4d2a89 100644 --- a/app/views/registrar/domains/form/_general.haml +++ b/app/views/registrar/domains/form/_general.haml @@ -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) diff --git a/config/app.yml b/config/app.yml index 763da3373..f11189111 100644 --- a/config/app.yml +++ b/config/app.yml @@ -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" diff --git a/config/locales/admin/disputes.en.yml b/config/locales/admin/disputes.en.yml new file mode 100644 index 000000000..9632dde4a --- /dev/null +++ b/config/locales/admin/disputes.en.yml @@ -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 diff --git a/config/locales/admin/menu.en.yml b/config/locales/admin/menu.en.yml index 2c31a5193..617341c6a 100644 --- a/config/locales/admin/menu.en.yml +++ b/config/locales/admin/menu.en.yml @@ -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 diff --git a/config/locales/en.yml b/config/locales/en.yml index cf72b1027..a825b1dc0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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?' diff --git a/config/routes.rb b/config/routes.rb index 17045edbf..1c03129db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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] diff --git a/db/migrate/20200421093637_create_disputes.rb b/db/migrate/20200421093637_create_disputes.rb new file mode 100644 index 000000000..b934d3297 --- /dev/null +++ b/db/migrate/20200421093637_create_disputes.rb @@ -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 diff --git a/db/migrate/20200505150413_add_dispute_period_in_months_to_setting.rb b/db/migrate/20200505150413_add_dispute_period_in_months_to_setting.rb new file mode 100644 index 000000000..cffa91b7f --- /dev/null +++ b/db/migrate/20200505150413_add_dispute_period_in_months_to_setting.rb @@ -0,0 +1,5 @@ +class AddDisputePeriodInMonthsToSetting < ActiveRecord::Migration[5.2] + def change + Setting.create(var: 'dispute_period_in_months', value: 36) + end +end diff --git a/db/migrate/20200518104105_add_closed_date_time_and_updator_to_dispute.rb b/db/migrate/20200518104105_add_closed_date_time_and_updator_to_dispute.rb new file mode 100644 index 000000000..1aae02e06 --- /dev/null +++ b/db/migrate/20200518104105_add_closed_date_time_and_updator_to_dispute.rb @@ -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 diff --git a/db/structure.sql b/db/structure.sql index e649e5bc1..4104b2db5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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'); + diff --git a/lib/tasks/whois.rake b/lib/tasks/whois.rake index c38b2c5ba..52be7e17f 100644 --- a/lib/tasks/whois.rake +++ b/lib/tasks/whois.rake @@ -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 diff --git a/test/fixtures/disputes.yml b/test/fixtures/disputes.yml new file mode 100644 index 000000000..a999fa0a1 --- /dev/null +++ b/test/fixtures/disputes.yml @@ -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' %> diff --git a/test/integration/admin_area/disputes_test.rb b/test/integration/admin_area/disputes_test.rb new file mode 100644 index 000000000..81019bb66 --- /dev/null +++ b/test/integration/admin_area/disputes_test.rb @@ -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 diff --git a/test/jobs/dispute_status_update_job_test.rb b/test/jobs/dispute_status_update_job_test.rb new file mode 100644 index 000000000..e70e58c04 --- /dev/null +++ b/test/jobs/dispute_status_update_job_test.rb @@ -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 diff --git a/test/models/disputed_domain_test.rb b/test/models/disputed_domain_test.rb new file mode 100644 index 000000000..01897e19b --- /dev/null +++ b/test/models/disputed_domain_test.rb @@ -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 diff --git a/test/system/admin_area/bank_statement_test.rb b/test/system/admin_area/bank_statement_test.rb index c95035a8d..29ed4f312 100644 --- a/test/system/admin_area/bank_statement_test.rb +++ b/test/system/admin_area/bank_statement_test.rb @@ -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')