diff --git a/CHANGELOG.md b/CHANGELOG.md
index 535d7d356..81ddffa7b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+24.01.2017
+* Disallow EPP domain:update/transfer/delete if a domain has "deleteCandidate" status
+
08.01.2017
* EPP XML schema "eis-1.0.xsd" replaced with "ee-1.1.xsd"
* .ddoc legal document format support dropped
diff --git a/app/jobs/domain_set_delete_candidate_job.rb b/app/jobs/domain_set_delete_candidate_job.rb
deleted file mode 100644
index f1e489694..000000000
--- a/app/jobs/domain_set_delete_candidate_job.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class DomainSetDeleteCandidateJob < Que::Job
-
- def run(domain_id)
- domain = Domain.find(domain_id)
- domain.statuses << DomainStatus::DELETE_CANDIDATE
- ::PaperTrail.whodunnit = "job - #{self.class.name}"
- domain.save(validate: false)
- DomainDeleteJob.enqueue(domain.id, run_at: rand(((24*60) - (DateTime.now.hour * 60 + DateTime.now.minute))).minutes.from_now)
- end
-end
diff --git a/app/models/concerns/domain/deletable.rb b/app/models/concerns/domain/deletable.rb
new file mode 100644
index 000000000..f724162e5
--- /dev/null
+++ b/app/models/concerns/domain/deletable.rb
@@ -0,0 +1,11 @@
+module Concerns::Domain::Deletable
+ extend ActiveSupport::Concern
+
+ included do
+ alias_attribute :delete_time, :delete_at
+ end
+
+ def discarded?
+ statuses.include?(DomainStatus::DELETE_CANDIDATE)
+ end
+end
diff --git a/app/models/domain.rb b/app/models/domain.rb
index bca5942ac..3e71dd1e9 100644
--- a/app/models/domain.rb
+++ b/app/models/domain.rb
@@ -5,6 +5,7 @@ class Domain < ActiveRecord::Base
include Concerns::Domain::Expirable
include Concerns::Domain::Activatable
include Concerns::Domain::ForceDelete
+ include Concerns::Domain::Deletable
has_paper_trail class_name: "DomainVersion", meta: { children: :children_log }
@@ -14,7 +15,6 @@ class Domain < ActiveRecord::Base
alias_attribute :on_hold_time, :outzone_at
alias_attribute :outzone_time, :outzone_at
- alias_attribute :delete_time, :delete_at
# TODO: whois requests ip whitelist for full info for own domains and partial info for other domains
# TODO: most inputs should be trimmed before validatation, probably some global logic?
diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb
index 6a2edf5ed..a2a5dd1dc 100644
--- a/app/models/epp/domain.rb
+++ b/app/models/epp/domain.rb
@@ -472,6 +472,9 @@ class Epp::Domain < Domain
# rubocop: disable Metrics/CyclomaticComplexity
def update(frame, current_user, verify = true)
return super if frame.blank?
+
+ check_discarded
+
at = {}.with_indifferent_access
at.deep_merge!(attrs_from(frame.css('chg'), current_user, 'chg'))
at.deep_merge!(attrs_from(frame.css('rem'), current_user, 'rem'))
@@ -563,6 +566,8 @@ class Epp::Domain < Domain
def epp_destroy(frame, user_id)
return false unless valid?
+ check_discarded
+
if doc = attach_legal_document(Epp::Domain.parse_legal_document_from_frame(frame))
frame.css("legalDocument").first.content = doc.path if doc && doc.persisted?
end
@@ -629,6 +634,8 @@ class Epp::Domain < Domain
# rubocop: disable Metrics/CyclomaticComplexity
def transfer(frame, action, current_user)
+ check_discarded
+
@is_transfer = true
case action
@@ -925,5 +932,16 @@ class Epp::Domain < Domain
res
end
end
+
+ private
+
+ def check_discarded
+ if discarded?
+ throw :epp_error, {
+ code: '2105',
+ msg: I18n.t(:object_is_not_eligible_for_renewal),
+ }
+ end
+ end
end
# rubocop: enable Metrics/ClassLength
diff --git a/spec/factories/domain.rb b/spec/factories/domain.rb
index edbd2274c..4c1b4b1cc 100644
--- a/spec/factories/domain.rb
+++ b/spec/factories/domain.rb
@@ -15,5 +15,9 @@ FactoryGirl.define do
force_delete_time nil
statuses []
end
+
+ factory :domain_discarded do
+ statuses [DomainStatus::DELETE_CANDIDATE]
+ end
end
end
diff --git a/spec/models/concerns/domain/deletable_spec.rb b/spec/models/concerns/domain/deletable_spec.rb
new file mode 100644
index 000000000..826299b69
--- /dev/null
+++ b/spec/models/concerns/domain/deletable_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+RSpec.describe Domain, db: false do
+ it { is_expected.to alias_attribute(:delete_time, :delete_at) }
+
+ describe '#discarded?' do
+ context 'when :deleteCandidate status is present' do
+ let(:domain) { described_class.new(statuses: [DomainStatus::DELETE_CANDIDATE]) }
+
+ specify { expect(domain).to be_discarded }
+ end
+
+ context 'when :deleteCandidate status is absent' do
+ let(:domain) { described_class.new(statuses: []) }
+
+ specify { expect(domain).to_not be_discarded }
+ end
+ end
+end
diff --git a/spec/models/domain_spec.rb b/spec/models/domain_spec.rb
index 6e224080f..65c96dbe8 100644
--- a/spec/models/domain_spec.rb
+++ b/spec/models/domain_spec.rb
@@ -607,7 +607,6 @@ end
RSpec.describe Domain, db: false do
it { is_expected.to alias_attribute(:on_hold_time, :outzone_at) }
- it { is_expected.to alias_attribute(:delete_time, :delete_at) }
it { is_expected.to alias_attribute(:outzone_time, :outzone_at) }
describe 'nameserver validation', db: true do
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 402db389d..bfe87211b 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -6,6 +6,7 @@ require 'capybara/poltergeist'
require 'paper_trail/frameworks/rspec'
require 'money-rails/test_helpers'
require 'support/requests/session_helpers'
+require 'support/requests/epp_helpers'
require 'support/features/session_helpers'
if ENV['ROBOT']
@@ -15,6 +16,7 @@ end
require 'support/matchers/alias_attribute'
require 'support/matchers/active_job'
+require 'support/matchers/epp/code'
require 'support/capybara'
require 'support/factory_girl'
require 'support/database_cleaner'
@@ -30,6 +32,8 @@ RSpec.configure do |config|
config.include Features::SessionHelpers, type: :feature
config.include AbstractController::Translation, type: :feature
+ config.include Requests::EPPHelpers, epp: true
+
config.define_derived_metadata(file_path: %r[/spec/features/]) do |metadata|
metadata[:db] = true if metadata[:db].nil?
end
@@ -46,6 +50,10 @@ RSpec.configure do |config|
metadata[:db] = true if metadata[:db].nil?
end
+ config.define_derived_metadata(file_path: %r[/spec/requests/epp/]) do |metadata|
+ metadata[:epp] = true if metadata[:epp].nil?
+ end
+
config.define_derived_metadata(file_path: %r[/spec/api/]) do |metadata|
metadata[:type] = :request
end
diff --git a/spec/requests/epp/domain/delete/discarded_spec.rb b/spec/requests/epp/domain/delete/discarded_spec.rb
new file mode 100644
index 000000000..91a527890
--- /dev/null
+++ b/spec/requests/epp/domain/delete/discarded_spec.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+RSpec.describe 'EPP domain:delete' do
+ let(:request_xml) { <<-XML
+
+
+
+
+
+ test.com
+
+
+
+
+ dGVzdCBmYWlsCg==
+
+
+
+
+ XML
+ }
+
+ before :example do
+ sign_in_to_epp_area
+ end
+
+ context 'when domain is not discarded' do
+ let!(:domain) { create(:domain, name: 'test.com') }
+
+ it 'returns epp code of 1001' do
+ post '/epp/command/delete', frame: request_xml
+ expect(response).to have_code_of(1001)
+ end
+ end
+
+ context 'when domain is discarded' do
+ let!(:domain) { create(:domain_discarded, name: 'test.com') }
+
+ it 'returns epp code of 2105' do
+ post '/epp/command/delete', frame: request_xml
+ expect(response).to have_code_of(2105)
+ end
+ end
+end
diff --git a/spec/requests/epp/domain/transfer/discarded_spec.rb b/spec/requests/epp/domain/transfer/discarded_spec.rb
new file mode 100644
index 000000000..51f65c641
--- /dev/null
+++ b/spec/requests/epp/domain/transfer/discarded_spec.rb
@@ -0,0 +1,42 @@
+require 'rails_helper'
+
+RSpec.describe 'EPP domain:transfer' do
+ let(:request_xml) { <<-XML
+
+
+
+
+
+ test.com
+
+ 98oiewslkfkd
+
+
+
+
+
+ XML
+ }
+
+ before :example do
+ sign_in_to_epp_area
+ end
+
+ context 'when domain is not discarded' do
+ let!(:domain) { create(:domain, name: 'test.com') }
+
+ it 'returns epp code of 1000' do
+ post '/epp/command/transfer', frame: request_xml
+ expect(response).to have_code_of(1000)
+ end
+ end
+
+ context 'when domain is discarded' do
+ let!(:domain) { create(:domain_discarded, name: 'test.com') }
+
+ it 'returns epp code of 2105' do
+ post '/epp/command/transfer', frame: request_xml
+ expect(response).to have_code_of(2105)
+ end
+ end
+end
diff --git a/spec/requests/epp/domain/update/discarded_spec.rb b/spec/requests/epp/domain/update/discarded_spec.rb
new file mode 100644
index 000000000..14ff1c743
--- /dev/null
+++ b/spec/requests/epp/domain/update/discarded_spec.rb
@@ -0,0 +1,39 @@
+require 'rails_helper'
+
+RSpec.describe 'EPP domain:update' do
+ let(:request_xml) { <<-XML
+
+
+
+
+
+ test.com
+
+
+
+
+ XML
+ }
+
+ before :example do
+ sign_in_to_epp_area
+ end
+
+ context 'when domain is not discarded' do
+ let!(:domain) { create(:domain, name: 'test.com') }
+
+ it 'returns epp code of 1000' do
+ post '/epp/command/update', frame: request_xml
+ expect(response).to have_code_of(1000)
+ end
+ end
+
+ context 'when domain is discarded' do
+ let!(:domain) { create(:domain_discarded, name: 'test.com') }
+
+ it 'returns epp code of 2105' do
+ post '/epp/command/update', frame: request_xml
+ expect(response).to have_code_of(2105)
+ end
+ end
+end
diff --git a/spec/support/matchers/epp/code.rb b/spec/support/matchers/epp/code.rb
new file mode 100644
index 000000000..db79488b3
--- /dev/null
+++ b/spec/support/matchers/epp/code.rb
@@ -0,0 +1,35 @@
+module Matchers
+ module EPP
+ class Code
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(response)
+ @xml = response.body
+ actual == expected
+ end
+
+ def failure_message
+ "Expected EPP code of #{expected}, got #{actual} (#{description})"
+ end
+
+ private
+
+ attr_reader :xml
+ attr_reader :expected
+
+ def actual
+ xml_document.xpath('//xmlns:result').first['code'].to_i
+ end
+
+ def description
+ xml_document.css('result msg').text
+ end
+
+ def xml_document
+ @xml_document ||= Nokogiri::XML(xml)
+ end
+ end
+ end
+end
diff --git a/spec/support/requests/epp_helpers.rb b/spec/support/requests/epp_helpers.rb
new file mode 100644
index 000000000..1372ae779
--- /dev/null
+++ b/spec/support/requests/epp_helpers.rb
@@ -0,0 +1,7 @@
+module Requests
+ module EPPHelpers
+ def have_code_of(*args)
+ Matchers::EPP::Code.new(*args)
+ end
+ end
+end