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 %> + +
As per domain law, expiry time is <%= Setting.dispute_period_in_months / 12 %> years ahead from start date.
+Active disputes
++ <%= 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' %> + | +
Expired / Closed disputes
++ <%= 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 %> + | +