diff --git a/.codeclimate.yml b/.codeclimate.yml
index be8f1df7a..294b9335c 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -27,3 +27,4 @@ exclude_patterns:
- "vendor/"
- "test/"
- "spec/"
+ - "CHANGELOG.md"
diff --git a/app/controllers/admin/domains/registry_lock_controller.rb b/app/controllers/admin/domains/registry_lock_controller.rb
new file mode 100644
index 000000000..cd04c5f2a
--- /dev/null
+++ b/app/controllers/admin/domains/registry_lock_controller.rb
@@ -0,0 +1,21 @@
+module Admin
+ module Domains
+ class RegistryLockController < BaseController
+ def destroy
+ set_domain
+ authorize! :manage, @domain
+ if @domain.remove_registry_lock
+ redirect_to edit_admin_domain_url(@domain), notice: t('.success')
+ else
+ redirect_to edit_admin_domain_url(@domain), alert: t('.error')
+ end
+ end
+
+ private
+
+ def set_domain
+ @domain = Domain.find(params[:domain_id])
+ end
+ end
+ end
+end
diff --git a/app/controllers/admin/domains_controller.rb b/app/controllers/admin/domains_controller.rb
index 1a525a72c..69276df0e 100644
--- a/app/controllers/admin/domains_controller.rb
+++ b/app/controllers/admin/domains_controller.rb
@@ -1,7 +1,7 @@
module Admin
class DomainsController < BaseController
- load_and_authorize_resource
- before_action :set_domain, only: [:show, :edit, :update, :zonefile]
+ before_action :set_domain, only: %i[show edit update keep]
+ authorize_resource
helper_method :force_delete_templates
def index
@@ -33,7 +33,8 @@ module Admin
end
def show
- @domain.valid?
+ # Validation is needed to warn users
+ @domain.validate
end
def edit
@@ -60,6 +61,11 @@ module Admin
@versions = @domain.versions
end
+ def keep
+ @domain.keep
+ redirect_to edit_admin_domain_url(@domain), notice: t('.kept')
+ end
+
private
def set_domain
diff --git a/app/controllers/admin/mail_templates_controller.rb b/app/controllers/admin/mail_templates_controller.rb
index 93141ade6..d7ec4e6d0 100644
--- a/app/controllers/admin/mail_templates_controller.rb
+++ b/app/controllers/admin/mail_templates_controller.rb
@@ -47,7 +47,7 @@ module Admin
def destroy
@mail_template = MailTemplate.find(params[:id])
if @mail_template.destroy
- redirect_to admin_mail_templates_path, notise: t(:deleted)
+ redirect_to admin_mail_templates_path, notice: t(:deleted)
else
flash.now[:alert] = I18n.t(:failure)
render 'show'
diff --git a/app/controllers/api/v1/registrant/base_controller.rb b/app/controllers/api/v1/registrant/base_controller.rb
index 4df0d226c..4497d68e6 100644
--- a/app/controllers/api/v1/registrant/base_controller.rb
+++ b/app/controllers/api/v1/registrant/base_controller.rb
@@ -6,6 +6,7 @@ module Api
module Registrant
class BaseController < ActionController::API
before_action :authenticate
+ before_action :set_paper_trail_whodunnit
rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
error = {}
@@ -22,16 +23,32 @@ module Api
header.gsub(pattern, '') if header&.match(pattern)
end
+ def associated_domains(user)
+ country_code, ident = user.registrant_ident.split('-')
+
+ BusinessRegistryCache.fetch_associated_domains(ident, country_code)
+ rescue Soap::Arireg::NotAvailableError => error
+ Rails.logger.fatal("[EXCEPTION] #{error}")
+ user.domains
+ end
+
def authenticate
decryptor = AuthTokenDecryptor.create_with_defaults(bearer_token)
decryptor.decrypt_token
if decryptor.valid?
- sign_in decryptor.user
+ sign_in(:registrant_user, decryptor.user)
else
- render json: { errors: [{base: ['Not authorized']}] }, status: :unauthorized
+ render json: { errors: [{ base: ['Not authorized'] }] },
+ status: :unauthorized
end
end
+
+ # This controller does not inherit from ApplicationController,
+ # so user_for_paper_trail method is not usable.
+ def set_paper_trail_whodunnit
+ ::PaperTrail.whodunnit = current_registrant_user.id_role_username
+ end
end
end
end
diff --git a/app/controllers/api/v1/registrant/domains_controller.rb b/app/controllers/api/v1/registrant/domains_controller.rb
index 97925701a..a416aaec0 100644
--- a/app/controllers/api/v1/registrant/domains_controller.rb
+++ b/app/controllers/api/v1/registrant/domains_controller.rb
@@ -30,17 +30,6 @@ module Api
render json: { errors: [{ base: ['Domain not found'] }] }, status: :not_found
end
end
-
- private
-
- def associated_domains(user)
- country_code, ident = user.registrant_ident.split('-')
-
- BusinessRegistryCache.fetch_associated_domains(ident, country_code)
- rescue Soap::Arireg::NotAvailableError => error
- Rails.logger.fatal("[EXCEPTION] #{error}")
- user.domains
- end
end
end
end
diff --git a/app/controllers/api/v1/registrant/registry_locks_controller.rb b/app/controllers/api/v1/registrant/registry_locks_controller.rb
new file mode 100644
index 000000000..27c98cc9f
--- /dev/null
+++ b/app/controllers/api/v1/registrant/registry_locks_controller.rb
@@ -0,0 +1,48 @@
+module Api
+ module V1
+ module Registrant
+ class RegistryLocksController < BaseController
+ before_action :set_domain
+ before_action :authorized_to_manage_locks?
+
+ def create
+ if @domain.apply_registry_lock
+ render json: @domain
+ else
+ render json: { errors: [{ base: ['Domain cannot be locked'] }] },
+ status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ if @domain.remove_registry_lock
+ render json: @domain
+ else
+ render json: { errors: [{ base: ['Domain is not locked'] }] },
+ status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def set_domain
+ domain_pool = current_registrant_user.domains
+ @domain = domain_pool.find_by(uuid: params[:domain_uuid])
+
+ return if @domain
+ render json: { errors: [{ base: ['Domain not found'] }] },
+ status: :not_found and return
+ end
+
+ def authorized_to_manage_locks?
+ return if current_registrant_user.administered_domains.include?(@domain)
+
+ render json: { errors: [
+ { base: ['Only administrative contacts can manage registry locks'] }
+ ] },
+ status: :unauthorized and return
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/domain/deletable.rb b/app/models/concerns/domain/deletable.rb
index 86c296d88..8640277c8 100644
--- a/app/models/concerns/domain/deletable.rb
+++ b/app/models/concerns/domain/deletable.rb
@@ -1,16 +1,25 @@
module Concerns::Domain::Deletable
extend ActiveSupport::Concern
- included do
- alias_attribute :delete_time, :delete_at
+ private
+
+ def delete_later
+ deletion_time = Time.zone.at(rand(deletion_time_span))
+ DomainDeleteJob.enqueue(id, run_at: deletion_time, priority: 1)
+ logger.info "Domain #{name} is scheduled to be deleted around #{deletion_time}"
end
- def discard
- statuses << DomainStatus::DELETE_CANDIDATE
- save
+ def do_not_delete_later
+ # Que job can be manually deleted in admin area UI
+ QueJob.find_by("args->>0 = '#{id}'", job_class: DomainDeleteJob.name)&.destroy
end
- def discarded?
- statuses.include?(DomainStatus::DELETE_CANDIDATE)
+ def deletion_time_span
+ range_params = [Time.zone.now.to_i, deletion_deadline.to_i].sort
+ Range.new(*range_params)
end
-end
+
+ def deletion_deadline
+ delete_at + 24.hours
+ end
+end
\ No newline at end of file
diff --git a/app/models/concerns/domain/discardable.rb b/app/models/concerns/domain/discardable.rb
new file mode 100644
index 000000000..e46492220
--- /dev/null
+++ b/app/models/concerns/domain/discardable.rb
@@ -0,0 +1,40 @@
+module Concerns::Domain::Discardable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def discard_domains
+ domains = where('delete_at < ? AND ? != ALL(coalesce(statuses, array[]::varchar[])) AND' \
+ ' ? != ALL(COALESCE(statuses, array[]::varchar[]))',
+ Time.zone.now,
+ DomainStatus::SERVER_DELETE_PROHIBITED,
+ DomainStatus::DELETE_CANDIDATE)
+
+ domains.each do |domain|
+ domain.discard
+ yield domain if block_given?
+ end
+ end
+ end
+
+ def discard
+ raise 'Domain is already discarded' if discarded?
+
+ statuses << DomainStatus::DELETE_CANDIDATE
+ transaction do
+ save(validate: false)
+ delete_later
+ end
+ end
+
+ def keep
+ statuses.delete(DomainStatus::DELETE_CANDIDATE)
+ transaction do
+ save(validate: false)
+ do_not_delete_later
+ end
+ end
+
+ def discarded?
+ statuses.include?(DomainStatus::DELETE_CANDIDATE)
+ end
+end
diff --git a/app/models/concerns/domain/registry_lockable.rb b/app/models/concerns/domain/registry_lockable.rb
new file mode 100644
index 000000000..4a759296d
--- /dev/null
+++ b/app/models/concerns/domain/registry_lockable.rb
@@ -0,0 +1,51 @@
+module Concerns
+ module Domain
+ module RegistryLockable
+ extend ActiveSupport::Concern
+
+ def apply_registry_lock
+ return unless registry_lockable?
+ return if locked_by_registrant?
+
+ transaction do
+ statuses << DomainStatus::SERVER_UPDATE_PROHIBITED
+ statuses << DomainStatus::SERVER_DELETE_PROHIBITED
+ statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED
+ self.locked_by_registrant_at = Time.zone.now
+
+ save!
+ end
+ end
+
+ def registry_lockable?
+ (statuses & [DomainStatus::PENDING_DELETE_CONFIRMATION,
+ DomainStatus::PENDING_CREATE, DomainStatus::PENDING_UPDATE,
+ DomainStatus::PENDING_DELETE, DomainStatus::PENDING_RENEW,
+ DomainStatus::PENDING_TRANSFER, DomainStatus::FORCE_DELETE]).empty?
+ end
+
+ def locked_by_registrant?
+ return false unless locked_by_registrant_at
+
+ lock_statuses = [DomainStatus::SERVER_UPDATE_PROHIBITED,
+ DomainStatus::SERVER_DELETE_PROHIBITED,
+ DomainStatus::SERVER_TRANSFER_PROHIBITED]
+
+ (statuses & lock_statuses).count == 3
+ end
+
+ def remove_registry_lock
+ return unless locked_by_registrant?
+
+ transaction do
+ statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED)
+ statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED)
+ statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED)
+ self.locked_by_registrant_at = nil
+
+ save!
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/versions.rb b/app/models/concerns/versions.rb
index 53a984179..77bc484ae 100644
--- a/app/models/concerns/versions.rb
+++ b/app/models/concerns/versions.rb
@@ -37,7 +37,7 @@ module Versions
registrar = Registrar.find_by(name: str)
user = registrar.api_users.first if registrar
- str_match = str.match(/^(\d+)-(ApiUser:|api-|AdminUser:)/)
+ str_match = str.match(/^(\d+)-(ApiUser:|api-|AdminUser:|RegistrantUser:)/)
user ||= User.find_by(id: str_match[1]) if str_match
user
diff --git a/app/models/domain.rb b/app/models/domain.rb
index 33f8a8f09..511f36006 100644
--- a/app/models/domain.rb
+++ b/app/models/domain.rb
@@ -4,8 +4,10 @@ class Domain < ActiveRecord::Base
include Concerns::Domain::Expirable
include Concerns::Domain::Activatable
include Concerns::Domain::ForceDelete
+ include Concerns::Domain::Discardable
include Concerns::Domain::Deletable
include Concerns::Domain::Transferable
+ include Concerns::Domain::RegistryLockable
has_paper_trail class_name: "DomainVersion", meta: { children: :children_log }
@@ -249,13 +251,6 @@ class Domain < ActiveRecord::Base
true
end
- def delete_candidateable?
- return false if delete_at > Time.zone.now
- return false if statuses.include?(DomainStatus::DELETE_CANDIDATE)
- return false if statuses.include?(DomainStatus::SERVER_DELETE_PROHIBITED)
- 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
@@ -613,10 +608,6 @@ class Domain < ActiveRecord::Base
where("#{attribute_alias(:outzone_time)} < ?", Time.zone.now)
end
- def self.delete_candidates
- where("#{attribute_alias(:delete_time)} < ?", Time.zone.now)
- end
-
def self.uses_zone?(zone)
exists?(["name ILIKE ?", "%.#{zone.origin}"])
end
diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb
index 8de52b226..2d208d647 100644
--- a/app/models/domain_cron.rb
+++ b/app/models/domain_cron.rb
@@ -84,22 +84,6 @@ class DomainCron
c = 0
- domains = Domain.delete_candidates
-
- domains.each do |domain|
- next unless domain.delete_candidateable?
-
- domain.statuses << DomainStatus::DELETE_CANDIDATE
-
- # If domain successfully saved, add it to delete schedule
- if domain.save(validate: false)
- ::PaperTrail.whodunnit = "cron - #{__method__}"
- DomainDeleteJob.enqueue(domain.id, run_at: rand(((24*60) - (DateTime.now.hour * 60 + DateTime.now.minute))).minutes.from_now)
- STDOUT << "#{Time.zone.now.utc} DomainCron.destroy_delete_candidates: job added by deleteCandidate status ##{domain.id} (#{domain.name})\n" unless Rails.env.test?
- c += 1
- end
- end
-
Domain.where('force_delete_at <= ?', Time.zone.now.end_of_day.utc).each do |x|
DomainDeleteJob.enqueue(x.id, run_at: rand(((24*60) - (DateTime.now.hour * 60 + DateTime.now.minute))).minutes.from_now)
STDOUT << "#{Time.zone.now.utc} DomainCron.destroy_delete_candidates: job added by force delete time ##{x.id} (#{x.name})\n" unless Rails.env.test?
diff --git a/app/models/domain_status.rb b/app/models/domain_status.rb
index 4da6a4a3c..2f7a457dd 100644
--- a/app/models/domain_status.rb
+++ b/app/models/domain_status.rb
@@ -153,13 +153,11 @@ class DomainStatus < ActiveRecord::Base
[
['Hold', SERVER_HOLD],
['ManualInzone', SERVER_MANUAL_INZONE],
- # [''],
['RenewProhibited', SERVER_RENEW_PROHIBITED],
['TransferProhibited', SERVER_TRANSFER_PROHIBITED],
['RegistrantChangeProhibited', SERVER_REGISTRANT_CHANGE_PROHIBITED],
['AdminChangeProhibited', SERVER_ADMIN_CHANGE_PROHIBITED],
['TechChangeProhibited', SERVER_TECH_CHANGE_PROHIBITED],
- # [''],
['UpdateProhibited', SERVER_UPDATE_PROHIBITED],
['DeleteProhibited', SERVER_DELETE_PROHIBITED]
]
@@ -171,11 +169,11 @@ class DomainStatus < ActiveRecord::Base
INACTIVE,
FORCE_DELETE,
PENDING_CREATE,
- #PENDING_DELETE,
PENDING_RENEW,
PENDING_TRANSFER,
PENDING_UPDATE,
- PENDING_DELETE_CONFIRMATION
+ PENDING_DELETE_CONFIRMATION,
+ DELETE_CANDIDATE,
]
end
end
diff --git a/app/models/que_job.rb b/app/models/que_job.rb
new file mode 100644
index 000000000..5eed581fc
--- /dev/null
+++ b/app/models/que_job.rb
@@ -0,0 +1,4 @@
+# To be able to remove existing jobs
+class QueJob < ActiveRecord::Base
+ self.primary_key = 'job_id'
+end
diff --git a/app/models/registrant_user.rb b/app/models/registrant_user.rb
index f47b924f6..ddbf2e664 100644
--- a/app/models/registrant_user.rb
+++ b/app/models/registrant_user.rb
@@ -1,5 +1,5 @@
class RegistrantUser < User
- ACCEPTED_ISSUER = 'AS Sertifitseerimiskeskus'
+ ACCEPTED_ISSUER = 'AS Sertifitseerimiskeskus'.freeze
attr_accessor :idc_data
devise :database_authenticatable, :trackable, :timeoutable
@@ -10,16 +10,46 @@ class RegistrantUser < User
delegate :can?, :cannot?, to: :ability
def ident
- registrant_ident.to_s.split("-").last
+ registrant_ident.to_s.split('-').last
end
+ def country_code
+ registrant_ident.to_s.split('-').first
+ end
+
+ # In Rails 5, can be replaced with a much simpler `or` query method and the raw SQL parts can be
+ # removed.
+ # https://guides.rubyonrails.org/active_record_querying.html#or-conditions
def domains
- ident_cc, ident = registrant_ident.to_s.split '-'
- Domain.includes(:registrar, :registrant).where(contacts: {
- ident_type: 'priv',
- ident: ident, #identity_code,
- ident_country_code: ident_cc #country_code
- })
+ domains_where_is_contact = begin
+ Domain.joins(:domain_contacts)
+ .where(domain_contacts: { contact_id: contacts })
+ end
+
+ domains_where_is_registrant = Domain.where(registrant_id: contacts)
+
+ Domain.from(
+ "(#{domains_where_is_registrant.to_sql} UNION " \
+ "#{domains_where_is_contact.to_sql}) AS domains"
+ )
+ end
+
+ def contacts
+ Contact.where(ident_type: 'priv', ident: ident, ident_country_code: country_code)
+ end
+
+ def administered_domains
+ domains_where_is_administrative_contact = begin
+ Domain.joins(:domain_contacts)
+ .where(domain_contacts: { contact_id: contacts, type: [AdminDomainContact] })
+ end
+
+ domains_where_is_registrant = Domain.where(registrant_id: contacts)
+
+ Domain.from(
+ "(#{domains_where_is_registrant.to_sql} UNION " \
+ "#{domains_where_is_administrative_contact.to_sql}) AS domains"
+ )
end
def to_s
@@ -35,13 +65,13 @@ class RegistrantUser < User
user_data = {}
# handling here new and old mode
- if idc_data.starts_with?("/")
+ if idc_data.starts_with?('/')
user_data[:ident] = idc_data.scan(/serialNumber=(\d+)/).flatten.first
user_data[:country_code] = idc_data.scan(/^\/C=(.{2})/).flatten.first
user_data[:first_name] = idc_data.scan(%r{/GN=(.+)/serialNumber}).flatten.first
user_data[:last_name] = idc_data.scan(%r{/SN=(.+)/GN}).flatten.first
else
- parse_str = "," + idc_data
+ parse_str = ',' + idc_data
user_data[:ident] = parse_str.scan(/,serialNumber=(\d+)/).flatten.first
user_data[:country_code] = parse_str.scan(/,C=(.{2})/).flatten.first
user_data[:first_name] = parse_str.scan(/,GN=([^,]+)/).flatten.first
diff --git a/app/presenters/domain_presenter.rb b/app/presenters/domain_presenter.rb
index b82d4df3f..445e5b87d 100644
--- a/app/presenters/domain_presenter.rb
+++ b/app/presenters/domain_presenter.rb
@@ -14,6 +14,11 @@ class DomainPresenter
html += " #{label}"
end
+ if domain.locked_by_registrant?
+ label = view.content_tag(:span, 'registryLock', class: 'label label-danger')
+ html += " #{label}"
+ end
+
html.html_safe
end
@@ -30,7 +35,7 @@ class DomainPresenter
end
def delete_date
- view.l(domain.delete_time, format: :date) if domain.delete_time
+ view.l(domain.delete_at, format: :date) if domain.delete_at
end
def force_delete_date
@@ -59,11 +64,30 @@ class DomainPresenter
end
end
+ def remove_registry_lock_btn
+ return unless domain.locked_by_registrant?
+
+ view.link_to(view.t('admin.domains.registry_lock.destroy.btn'),
+ view.admin_domain_registry_lock_path(domain),
+ method: :delete,
+ data: { confirm: view.t('admin.domains.registry_lock.destroy.confirm') },
+ class: 'dropdown-item')
+ end
+
+ def keep_btn
+ return unless domain.discarded?
+
+ view.link_to view.t('admin.domains.edit.keep_btn'), view.keep_admin_domain_path(@domain),
+ method: :patch,
+ data: { confirm: view.t('admin.domains.edit.keep_btn_confirm') },
+ class: 'dropdown-item'
+ end
+
private
def schedule_force_delete_btn
view.content_tag(:a, view.t('admin.domains.force_delete_toggle_btn.schedule'),
- class: 'btn btn-default',
+ class: 'dropdown-item',
data: {
toggle: 'modal',
target: '.domain-edit-force-delete-dialog',
@@ -77,14 +101,14 @@ class DomainPresenter
data: {
confirm: view.t('admin.domains.force_delete_toggle_btn.cancel_confirm'),
},
- class: 'btn btn-primary'
+ class: 'dropdown-item'
end
def inactive_schedule_force_delete_btn
view.content_tag :button, view.t('admin.domains.force_delete_toggle_btn.schedule'),
title: view.t('admin.domains.force_delete_toggle_btn.unable_to_schedule'),
disabled: true,
- class: 'btn btn-default'
+ class: 'dropdown-item'
end
attr_reader :domain
diff --git a/app/views/admin/domains/edit.html.erb b/app/views/admin/domains/edit.html.erb
index 30028e09e..2317bc330 100644
--- a/app/views/admin/domains/edit.html.erb
+++ b/app/views/admin/domains/edit.html.erb
@@ -5,18 +5,31 @@
<%= link_to @domain, admin_domain_path(@domain) %>
+
diff --git a/config/locales/admin/domains.en.yml b/config/locales/admin/domains.en.yml
index ed93eb647..7773331c8 100644
--- a/config/locales/admin/domains.en.yml
+++ b/config/locales/admin/domains.en.yml
@@ -13,8 +13,13 @@ en:
reset_btn: Reset
edit:
- header: "Edit: %{domain}"
+ header: "Edit:"
add_new_status_btn: Add new status
+ keep_btn: Remove deleteCandidate status
+ keep_btn_confirm: Are you sure you want to remove deleteCandidate status?
+
+ keep:
+ kept: deleteCandidate status has been removed
force_delete_dialog:
title: Force delete
@@ -23,6 +28,13 @@ en:
close_btn: Close dialog
submit_btn: Force delete domain
+ registry_lock:
+ destroy:
+ btn: Remove registry lock
+ confirm: Are you sure you want to remove the registry lock?
+ success: Registry lock removed
+ error: Registry lock could not be removed
+
versions:
time: Time
registrant: Registrant
@@ -33,6 +45,8 @@ en:
general:
outzone_time: Outzone time
delete_time: Delete time
+ force_delete_time: Force delete time
+ locked_by_registrant_at: Registry lock time
admin_contacts:
title: Admin. contacts
@@ -48,4 +62,4 @@ en:
unable_to_schedule: >
Force delete procedure cannot be scheduled while a domain has deleteCandidate status
cancel: Cancel force delete
- cancel_confirm: Are you sure you want cancel force delete procedure?
\ No newline at end of file
+ cancel_confirm: Are you sure you want cancel force delete procedure?
diff --git a/config/routes.rb b/config/routes.rb
index 1e235b41e..ff33ec652 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,7 +23,9 @@ Rails.application.routes.draw do
namespace :registrant do
post 'auth/eid', to: 'auth#eid'
- resources :domains, only: %i[index show], param: :uuid
+ resources :domains, only: %i[index show], param: :uuid do
+ resource :registry_lock, only: %i[create destroy]
+ end
resources :contacts, only: %i[index show], param: :uuid
end
end
@@ -191,11 +193,16 @@ Rails.application.routes.draw do
match 'forward', via: [:post, :get]
end
- resources :domains do
+ resources :domains, except: %i[new create destroy] do
resources :domain_versions, controller: 'domains', action: 'versions'
resources :pending_updates
resources :pending_deletes
resource :force_delete, controller: 'domains/force_delete', only: %i[create destroy]
+ resource :registry_lock, controller: 'domains/registry_lock', only: :destroy
+
+ member do
+ patch :keep
+ end
end
resources :domain_versions do
@@ -262,4 +269,4 @@ Rails.application.routes.draw do
# To prevent users seeing the default welcome message "Welcome aboard" from Rails
root to: redirect('admin/sign_in')
-end
\ No newline at end of file
+end
diff --git a/config/schedule.rb b/config/schedule.rb
index 6413a9c56..6ad7328b2 100644
--- a/config/schedule.rb
+++ b/config/schedule.rb
@@ -57,6 +57,10 @@ if @cron_group == 'registry'
every :day, at: '19:00pm' do
runner 'Directo.send_receipts'
end if @environment == 'production'
+
+ every 42.minutes do
+ rake 'domain:discard'
+ end
end
every 10.minutes do
diff --git a/db/migrate/20180808064402_add_registry_lock_time_column.rb b/db/migrate/20180808064402_add_registry_lock_time_column.rb
new file mode 100644
index 000000000..5bcbd2c7e
--- /dev/null
+++ b/db/migrate/20180808064402_add_registry_lock_time_column.rb
@@ -0,0 +1,7 @@
+class AddRegistryLockTimeColumn < ActiveRecord::Migration
+ def change
+ change_table(:domains) do |t|
+ t.column :locked_by_registrant_at, :datetime, null: true
+ end
+ end
+end
diff --git a/db/migrate/20180816123540_change_contacts_email_to_not_null.rb b/db/migrate/20180816123540_change_contacts_email_to_not_null.rb
new file mode 100644
index 000000000..e6080e38b
--- /dev/null
+++ b/db/migrate/20180816123540_change_contacts_email_to_not_null.rb
@@ -0,0 +1,5 @@
+class ChangeContactsEmailToNotNull < ActiveRecord::Migration
+ def change
+ change_column_null :contacts, :email, false
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20180824092855_change_domain_pending_json_to_jsonb.rb b/db/migrate/20180824092855_change_domain_pending_json_to_jsonb.rb
new file mode 100644
index 000000000..10639527a
--- /dev/null
+++ b/db/migrate/20180824092855_change_domain_pending_json_to_jsonb.rb
@@ -0,0 +1,9 @@
+class ChangeDomainPendingJsonToJsonb < ActiveRecord::Migration
+ def up
+ change_column :domains, :pending_json, 'jsonb USING CAST(pending_json AS jsonb)'
+ end
+
+ def down
+ change_column :domains, :pending_json, 'json USING CAST(pending_json AS json)'
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 14d5ffffd..668e96be5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -613,7 +613,7 @@ CREATE TABLE public.contacts (
id integer NOT NULL,
code character varying NOT NULL,
phone character varying,
- email character varying,
+ email character varying NOT NULL,
fax character varying,
created_at timestamp without time zone,
updated_at timestamp without time zone,
@@ -904,7 +904,7 @@ CREATE TABLE public.domains (
delete_at timestamp without time zone,
registrant_verification_asked_at timestamp without time zone,
registrant_verification_token character varying,
- pending_json json,
+ pending_json jsonb,
force_delete_at timestamp without time zone,
statuses character varying[],
reserved boolean DEFAULT false,
@@ -912,7 +912,8 @@ CREATE TABLE public.domains (
statuses_before_force_delete character varying[] DEFAULT '{}'::character varying[],
upid integer,
up_date timestamp without time zone,
- uuid uuid DEFAULT public.gen_random_uuid() NOT NULL
+ uuid uuid DEFAULT public.gen_random_uuid() NOT NULL,
+ locked_by_registrant_at timestamp without time zone
);
@@ -4761,3 +4762,9 @@ INSERT INTO schema_migrations (version) VALUES ('20180613045614');
INSERT INTO schema_migrations (version) VALUES ('20180713154915');
+INSERT INTO schema_migrations (version) VALUES ('20180808064402');
+
+INSERT INTO schema_migrations (version) VALUES ('20180816123540');
+
+INSERT INTO schema_migrations (version) VALUES ('20180824092855');
+
diff --git a/doc/registrant-api.md b/doc/registrant-api.md
index c6f063a91..b475733f4 100644
--- a/doc/registrant-api.md
+++ b/doc/registrant-api.md
@@ -7,5 +7,5 @@ Main communication specification through Registrant API:
[Authentication](registrant-api/v1/authentication.md)
[Domains](registrant-api/v1/domain.md)
-[Domain Lock](registrant-api/v1/domain_lock.md)
+[Registry Lock](registrant-api/v1/registry_lock.md)
[Contacts](registrant-api/v1/contact.md)
diff --git a/doc/registrant-api/v1/domain_lock.md b/doc/registrant-api/v1/registry_lock.md
similarity index 86%
rename from doc/registrant-api/v1/domain_lock.md
rename to doc/registrant-api/v1/registry_lock.md
index 5f275edb4..b8d440554 100644
--- a/doc/registrant-api/v1/domain_lock.md
+++ b/doc/registrant-api/v1/registry_lock.md
@@ -1,4 +1,4 @@
-# Domain locks
+# Registry lock
## POST api/v1/registrant/domains/$UUID/registry_lock
@@ -59,12 +59,12 @@ Content-Type: application/json
#### Response for failure
```
-HTTP/1.1 400
+HTTP/1.1 422
Content-Type: application/json
{
"errors": [
- { "base": "domain cannot be locked" }
+ { "base": "Domain cannot be locked" }
]
}
@@ -76,11 +76,23 @@ Content-Type: application/json
{
"errors": [
- { "base": "domain does not exist" }
+ { "base": "Domain not found" }
]
}
```
+```
+HTTP/1.1 401
+Content-Type: application/json
+
+{
+ "errors": [
+ { "base": ["Only administrative contacts can manage registry locks"] }
+ ]
+}
+
+```
+
## DELETE api/v1/registrant/domains/$UUID/registry_lock
@@ -139,12 +151,12 @@ Content-Type: application/json
#### Response for failure
```
-HTTP/1.1 400
+HTTP/1.1 422
Content-Type: application/json
{
"errors": [
- { "base": "domain cannot be unlocked" }
+ { "base": "Domain is not locked" }
]
}
@@ -156,7 +168,19 @@ Content-Type: application/json
{
"errors": [
- { "base": "domain does not exist" }
+ { "base": "Domain not found" }
+ ]
+}
+
+```
+
+```
+HTTP/1.1 401
+Content-Type: application/json
+
+{
+ "errors": [
+ { "base": ["Only administrative contacts can manage registry locks"] }
]
}
diff --git a/lib/tasks/domain.rake b/lib/tasks/domain.rake
new file mode 100644
index 000000000..a5cc6d557
--- /dev/null
+++ b/lib/tasks/domain.rake
@@ -0,0 +1,13 @@
+namespace :domain do
+ desc 'Discard domains'
+ task discard: :environment do
+ domain_count = 0
+
+ Domain.discard_domains do |domain|
+ puts "#{domain} is discarded"
+ domain_count = domain_count + 1
+ end
+
+ puts "Discarded total: #{domain_count}"
+ end
+end
\ No newline at end of file
diff --git a/spec/models/domain_spec.rb b/spec/models/domain_spec.rb
index 6b282d651..000a7be97 100644
--- a/spec/models/domain_spec.rb
+++ b/spec/models/domain_spec.rb
@@ -298,50 +298,6 @@ RSpec.describe Domain do
@domain.registrant_update_confirmable?('123').should == false
end
end
-
- context 'with versioning' do
- it 'should not have one version' do
- with_versioning do
- @domain.versions.size.should == 0
- @domain.name = 'new-test-name.ee'
- @domain.save
- @domain.errors.full_messages.should match_array([])
- @domain.versions.size.should == 1
- end
- end
-
- it 'should return api_creator when created by api user' do
- with_versioning do
- @user = create(:admin_user)
- @api_user = create(:api_user)
- @user.id.should == 1
- @api_user.id.should == 2
- ::PaperTrail.whodunnit = '2-ApiUser: testuser'
-
- @domain = create(:domain)
- @domain.creator_str.should == '2-ApiUser: testuser'
-
- @domain.creator.should == @api_user
- @domain.creator.should_not == @user
- end
- end
-
- it 'should return api_creator when created by api user' do
- with_versioning do
- @user = create(:admin_user, id: 1000)
- @api_user = create(:api_user, id: 2000)
- @user.id.should == 1000
- @api_user.id.should == 2000
- ::PaperTrail.whodunnit = '1000-AdminUser: testuser'
-
- @domain = create(:domain)
- @domain.creator_str.should == '1000-AdminUser: testuser'
-
- @domain.creator.should == @user
- @domain.creator.should_not == @api_user
- end
- end
- end
end
it 'validates domain name' do
@@ -839,22 +795,6 @@ RSpec.describe Domain do
end
end
- describe '::delete_candidates', db: true do
- before :example do
- travel_to Time.zone.parse('05.07.2010 00:00')
-
- create(:zone, origin: 'ee')
-
- create(:domain, id: 1, delete_time: Time.zone.parse('04.07.2010 23:59'))
- create(:domain, id: 2, delete_time: Time.zone.parse('05.07.2010 00:00'))
- create(:domain, id: 3, delete_time: Time.zone.parse('05.07.2010 00:01'))
- end
-
- it 'returns domains with delete time in the past' do
- expect(described_class.delete_candidates.ids).to eq([1])
- end
- end
-
describe '::uses_zone?', db: true do
let!(:zone) { create(:zone, origin: 'domain.tld') }
diff --git a/spec/presenters/domain_presenter_spec.rb b/spec/presenters/domain_presenter_spec.rb
index c83ca58ca..ff4fc2841 100644
--- a/spec/presenters/domain_presenter_spec.rb
+++ b/spec/presenters/domain_presenter_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe DomainPresenter do
subject(:delete_date) { presenter.delete_date }
context 'when present' do
- let(:domain) { instance_double(Domain, delete_time: '05.07.2010') }
+ let(:domain) { instance_double(Domain, delete_at: '05.07.2010') }
it 'returns localized date' do
expect(view).to receive(:l).with('05.07.2010', format: :date).and_return('delete date')
@@ -53,7 +53,7 @@ RSpec.describe DomainPresenter do
end
context 'when absent' do
- let(:domain) { instance_double(Domain, delete_time: nil) }
+ let(:domain) { instance_double(Domain, delete_at: nil) }
specify { expect(delete_date).to be_nil }
end
diff --git a/test/fixtures/contacts.yml b/test/fixtures/contacts.yml
index 49efe8b36..cdbf81399 100644
--- a/test/fixtures/contacts.yml
+++ b/test/fixtures/contacts.yml
@@ -17,7 +17,7 @@ william: &william
email: william@inbox.test
phone: '+555.555'
fax: '+666.6'
- ident: 1234
+ ident: 12345
ident_type: priv
ident_country_code: US
registrar: bestnames
@@ -36,7 +36,7 @@ jane:
name: Jane
email: jane@mail.test
phone: '+555.555'
- ident: 1234
+ ident: 123456
ident_type: priv
ident_country_code: US
registrar: bestnames
@@ -48,7 +48,7 @@ acme_ltd:
name: Acme Ltd
email: acme@outlook.test
phone: '+555.555'
- ident: 1234
+ ident: 1234567
ident_type: org
registrar: bestnames
ident_country_code: US
@@ -60,7 +60,7 @@ jack:
name: Jack
email: jack@inbox.test
phone: '+555.555'
- ident: 12345
+ ident: 12345678
ident_type: org
registrar: goodnames
ident_country_code: US
@@ -89,4 +89,4 @@ invalid:
email: invalid@invalid.test
auth_info: any
registrar: bestnames
- uuid: bd80c0f9-26ee-49e0-a2cb-2311d931c433
\ No newline at end of file
+ uuid: bd80c0f9-26ee-49e0-a2cb-2311d931c433
diff --git a/test/fixtures/domain_contacts.yml b/test/fixtures/domain_contacts.yml
index 43d082e31..5c5d89e24 100644
--- a/test/fixtures/domain_contacts.yml
+++ b/test/fixtures/domain_contacts.yml
@@ -28,11 +28,36 @@ airport_william_tech:
contact: william
type: TechDomainContact
-library_john:
+library_acme_admin:
+ domain: library
+ contact: acme_ltd
+ type: AdminDomainContact
+
+library_john_tech:
domain: library
contact: john
+ type: TechDomainContact
+
+metro_jack_admin:
+ domain: metro
+ contact: jack
type: AdminDomainContact
+metro_jack_tech:
+ domain: metro
+ contact: jack
+ type: TechDomainContact
+
+hospital_john_admin:
+ domain: hospital
+ contact: john
+ type: AdminDomainContact
+
+hospital_john_tech:
+ domain: hospital
+ contact: john
+ type: TechDomainContact
+
invalid_invalid_admin:
domain: invalid
contact: invalid
diff --git a/test/integration/api/registrant/registrant_api_registry_locks_test.rb b/test/integration/api/registrant/registrant_api_registry_locks_test.rb
new file mode 100644
index 000000000..bb50bdc1b
--- /dev/null
+++ b/test/integration/api/registrant/registrant_api_registry_locks_test.rb
@@ -0,0 +1,131 @@
+require 'test_helper'
+require 'auth_token/auth_token_creator'
+
+class RegistrantApiRegistryLocksTest < ApplicationIntegrationTest
+ def setup
+ super
+
+ @original_registry_time = Setting.days_to_keep_business_registry_cache
+ Setting.days_to_keep_business_registry_cache = 1
+ travel_to Time.zone.parse('2010-07-05')
+
+ @user = users(:registrant)
+ @domain = domains(:airport)
+ @auth_headers = { 'HTTP_AUTHORIZATION' => auth_token }
+ end
+
+ def teardown
+ super
+
+ Setting.days_to_keep_business_registry_cache = @original_registry_time
+ travel_back
+ end
+
+ def test_can_lock_a_not_locked_domain
+ post '/api/v1/registrant/domains/2df2c1a1-8f6a-490a-81be-8bdf29866880/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+
+ assert(response_json[:statuses].include?(DomainStatus::SERVER_DELETE_PROHIBITED))
+ assert(response_json[:statuses].include?(DomainStatus::SERVER_TRANSFER_PROHIBITED))
+ assert(response_json[:statuses].include?(DomainStatus::SERVER_UPDATE_PROHIBITED))
+
+ @domain.reload
+ assert(@domain.locked_by_registrant?)
+ end
+
+ def test_locking_a_domain_creates_a_version_record
+ assert_difference '@domain.versions.count', 1 do
+ post '/api/v1/registrant/domains/2df2c1a1-8f6a-490a-81be-8bdf29866880/registry_lock',
+ {}, @auth_headers
+ end
+
+ @domain.reload
+ assert_equal(@domain.updator, @user)
+ end
+
+ def test_cannot_lock_a_domain_in_pending_state
+ @domain.statuses << DomainStatus::PENDING_UPDATE
+ @domain.save
+
+ post '/api/v1/registrant/domains/2df2c1a1-8f6a-490a-81be-8bdf29866880/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+ assert_equal(422, response.status)
+ assert_equal({ errors: [{ base: ['Domain cannot be locked'] }] }, response_json)
+ end
+
+ def test_cannot_lock_an_already_locked_domain
+ @domain.apply_registry_lock
+ assert(@domain.locked_by_registrant?)
+
+ post '/api/v1/registrant/domains/2df2c1a1-8f6a-490a-81be-8bdf29866880/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+ assert_equal(422, response.status)
+ assert_equal({ errors: [{ base: ['Domain cannot be locked'] }] }, response_json)
+ end
+
+ def test_can_unlock_a_locked_domain
+ @domain.apply_registry_lock
+
+ delete '/api/v1/registrant/domains/2df2c1a1-8f6a-490a-81be-8bdf29866880/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+ assert(response_json[:statuses].include?(DomainStatus::OK))
+ @domain.reload
+ refute(@domain.locked_by_registrant?)
+ end
+
+ def test_cannot_unlock_a_not_locked_domain
+ delete '/api/v1/registrant/domains/2df2c1a1-8f6a-490a-81be-8bdf29866880/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+ assert_equal(422, response.status)
+ assert_equal({ errors: [{ base: ['Domain is not locked'] }] }, response_json)
+ end
+
+ def test_returns_404_when_domain_is_not_found
+ post '/api/v1/registrant/domains/random-uuid/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+ assert_equal(404, response.status)
+ assert_equal({ errors: [{ base: ['Domain not found'] }] }, response_json)
+ end
+
+ def test_technical_contact_cannot_lock_a_domain
+ post '/api/v1/registrant/domains/647bcc48-8d5e-4a04-8ce5-2a3cd17b6eab/registry_lock',
+ {}, @auth_headers
+
+ response_json = JSON.parse(response.body, symbolize_names: true)
+ assert_equal(401, response.status)
+ assert_equal({ errors: [{ base: ['Only administrative contacts can manage registry locks'] }] },
+ response_json)
+ end
+
+ def test_registrant_can_lock_a_domain
+ post '/api/v1/registrant/domains/1b3ee442-e8fe-4922-9492-8fcb9dccc69c/registry_lock',
+ {}, @auth_headers
+
+ assert_equal(200, response.status)
+ response_json = JSON.parse(response.body, symbolize_names: true)
+
+ assert(response_json[:statuses].include?(DomainStatus::SERVER_DELETE_PROHIBITED))
+ assert(response_json[:statuses].include?(DomainStatus::SERVER_TRANSFER_PROHIBITED))
+ assert(response_json[:statuses].include?(DomainStatus::SERVER_UPDATE_PROHIBITED))
+ end
+
+ private
+
+ def auth_token
+ token_creator = AuthTokenCreator.create_with_defaults(@user)
+ hash = token_creator.token_in_hash
+ "Bearer #{hash[:access_token]}"
+ end
+end
diff --git a/test/integration/epp/domain/domain_delete_test.rb b/test/integration/epp/domain/domain_delete_test.rb
index 61cd7d6f3..438e20eeb 100644
--- a/test/integration/epp/domain/domain_delete_test.rb
+++ b/test/integration/epp/domain/domain_delete_test.rb
@@ -1,6 +1,10 @@
require 'test_helper'
class EppDomainDeleteTest < ApplicationIntegrationTest
+ def setup
+ @domain = domains(:shop)
+ end
+
def test_bypasses_domain_and_registrant_and_contacts_validation
request_xml = <<-XML
@@ -27,7 +31,9 @@ class EppDomainDeleteTest < ApplicationIntegrationTest
end
def test_discarded_domain_cannot_be_deleted
- domains(:shop).discard
+ travel_to Time.zone.parse('2010-07-05 10:30')
+ @domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ @domain.discard
request_xml = <<-XML
@@ -51,5 +57,6 @@ class EppDomainDeleteTest < ApplicationIntegrationTest
post '/epp/command/delete', { frame: request_xml }, 'HTTP_COOKIE' => 'session=api_bestnames'
end
assert_equal '2105', Nokogiri::XML(response.body).at_css('result')[:code]
+ travel_back
end
end
diff --git a/test/integration/epp/domain/domain_update_test.rb b/test/integration/epp/domain/domain_update_test.rb
index f1db1b087..26996c63f 100644
--- a/test/integration/epp/domain/domain_update_test.rb
+++ b/test/integration/epp/domain/domain_update_test.rb
@@ -1,6 +1,10 @@
require 'test_helper'
class EppDomainUpdateTest < ApplicationIntegrationTest
+ def setup
+ @domain = domains(:shop)
+ end
+
def test_update_domain
request_xml = <<-XML
@@ -21,13 +25,16 @@ class EppDomainUpdateTest < ApplicationIntegrationTest
XML
post '/epp/command/update', { frame: request_xml }, 'HTTP_COOKIE' => 'session=api_bestnames'
- assert_equal 'f0ff7d17b0', domains(:shop).transfer_code
+ @domain.reload
+ assert_equal 'f0ff7d17b0', @domain.transfer_code
assert_equal '1000', Nokogiri::XML(response.body).at_css('result')[:code]
assert_equal 1, Nokogiri::XML(response.body).css('result').size
end
def test_discarded_domain_cannot_be_updated
- domains(:shop).discard
+ travel_to Time.zone.parse('2010-07-05 10:30')
+ @domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ @domain.discard
request_xml = <<-XML
@@ -44,5 +51,6 @@ class EppDomainUpdateTest < ApplicationIntegrationTest
post '/epp/command/update', { frame: request_xml }, 'HTTP_COOKIE' => 'session=api_bestnames'
assert_equal '2105', Nokogiri::XML(response.body).at_css('result')[:code]
+ travel_back
end
end
diff --git a/test/integration/epp/domain/transfer/request_test.rb b/test/integration/epp/domain/transfer/request_test.rb
index 6335fb27e..1a9f9ae75 100644
--- a/test/integration/epp/domain/transfer/request_test.rb
+++ b/test/integration/epp/domain/transfer/request_test.rb
@@ -1,12 +1,17 @@
require 'test_helper'
class EppDomainTransferRequestTest < ApplicationIntegrationTest
- setup do
+ def setup
@domain = domains(:shop)
@new_registrar = registrars(:goodnames)
+ @original_transfer_wait_time = Setting.transfer_wait_time
Setting.transfer_wait_time = 0
end
+ def teardown
+ Setting.transfer_wait_time = @original_transfer_wait_time
+ end
+
def test_transfers_domain_at_once
post '/epp/command/transfer', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_goodnames' }
assert_equal '1000', Nokogiri::XML(response.body).at_css('result')[:code]
@@ -75,14 +80,17 @@ class EppDomainTransferRequestTest < ApplicationIntegrationTest
assert_equal '2304', Nokogiri::XML(response.body).at_css('result')[:code]
end
- def test_discarded_domain
- @domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE])
+ def test_discarded_domain_cannot_be_transferred
+ travel_to Time.zone.parse('2010-07-05 10:30')
+ @domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ @domain.discard
post '/epp/command/transfer', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_goodnames' }
@domain.reload
assert_equal registrars(:bestnames), @domain.registrar
assert_equal '2105', Nokogiri::XML(response.body).at_css('result')[:code]
+ travel_back
end
def test_same_registrar
diff --git a/test/integration/tasks/discard_domain_test.rb b/test/integration/tasks/discard_domain_test.rb
new file mode 100644
index 000000000..0da7014c7
--- /dev/null
+++ b/test/integration/tasks/discard_domain_test.rb
@@ -0,0 +1,51 @@
+require 'test_helper'
+
+class DiscardDomainTaskTest < TaskTestCase
+ setup do
+ travel_to Time.zone.parse('2010-07-05 08:00')
+ @domain = domains(:shop)
+ end
+
+ def test_discard_domains_with_past_delete_at
+ @domain.update!(delete_at: Time.zone.parse('2010-07-05 07:59'))
+ Rake::Task['domain:discard'].execute
+ @domain.reload
+ assert @domain.discarded?
+ end
+
+ def test_ignore_domains_with_delete_at_in_the_future_or_now
+ @domain.update!(delete_at: Time.zone.parse('2010-07-05 08:00'))
+ Rake::Task['domain:discard'].execute
+ @domain.reload
+ refute @domain.discarded?
+ end
+
+ def test_ignore_already_discarded_domains
+ @domain.update!(delete_at: Time.zone.parse('2010-07-05 07:59'))
+ @domain.discard
+
+ job_count = lambda do
+ QueJob.where("args->>0 = '#{@domain.id}'", job_class: DomainDeleteJob.name).count
+ end
+
+ assert_no_difference job_count, 'A domain should not be discarded again' do
+ Rake::Task['domain:discard'].execute
+ end
+ end
+
+ def test_ignore_domains_with_server_delete_prohibited_status
+ @domain.update!(delete_at: Time.zone.parse('2010-07-05 07:59'),
+ statuses: [DomainStatus::SERVER_DELETE_PROHIBITED])
+ Rake::Task['domain:discard'].execute
+ @domain.reload
+ refute @domain.discarded?
+ end
+
+ def test_show_results
+ @domain.update!(delete_at: Time.zone.parse('2010-07-05 07:59'))
+ $stdout = StringIO.new
+
+ Rake::Task['domain:discard'].execute
+ assert_equal "shop.test is discarded\nDiscarded total: 1\n", $stdout.string
+ end
+end
\ No newline at end of file
diff --git a/test/models/domain/deletable_test.rb b/test/models/domain/deletable_test.rb
deleted file mode 100644
index dbec7bc7f..000000000
--- a/test/models/domain/deletable_test.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'test_helper'
-
-class DomainDeletableTest < ActiveSupport::TestCase
- setup do
- @domain = domains(:shop)
- end
-
- def test_discard
- refute @domain.discarded?
- @domain.discard
- @domain.reload
- assert @domain.discarded?
- end
-end
diff --git a/test/models/domain/discardable_test.rb b/test/models/domain/discardable_test.rb
new file mode 100644
index 000000000..0181a6bb4
--- /dev/null
+++ b/test/models/domain/discardable_test.rb
@@ -0,0 +1,63 @@
+require 'test_helper'
+
+class DomainDiscardableTest < ActiveSupport::TestCase
+ setup do
+ travel_to Time.zone.parse('2010-07-05 10:30')
+ @domain = domains(:shop)
+ @domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ end
+
+ teardown do
+ travel_back
+ end
+
+ def test_discarding_a_domain_persists_the_state
+ @domain.discard
+ @domain.reload
+ assert @domain.discarded?
+ end
+
+ def test_discarding_a_domain_schedules_deletion_at_random_time
+ @domain.discard
+ other_domain = domains(:airport)
+ other_domain.delete_at = Time.zone.parse('2010-07-04')
+ other_domain.discard
+
+ background_job = QueJob.find_by("args->>0 = '#{@domain.id}'", job_class: DomainDeleteJob.name)
+ other_background_job = QueJob.find_by("args->>0 = '#{other_domain.id}'",
+ job_class: DomainDeleteJob.name)
+ assert_not_equal background_job.run_at, other_background_job.run_at
+ end
+
+ def test_discarding_a_domain_bypasses_validation
+ domain = domains(:invalid)
+ domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ domain.discard
+ domain.reload
+ assert domain.discarded?
+ end
+
+ def test_domain_cannot_be_discarded_repeatedly
+ @domain.discard
+
+ exception = assert_raises do
+ @domain.discard
+ end
+ assert_equal 'Domain is already discarded', exception.message
+ end
+
+ def test_keeping_a_domain_bypasses_validation
+ domain = domains(:invalid)
+ domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ domain.discard
+ domain.keep
+ domain.reload
+ assert_not domain.discarded?
+ end
+
+ def test_keeping_a_domain_cancels_domain_deletion
+ @domain.discard
+ @domain.keep
+ assert_nil QueJob.find_by("args->>0 = '#{@domain.id}'", job_class: DomainDeleteJob.name)
+ end
+end
diff --git a/test/models/domain/domain_version_test.rb b/test/models/domain/domain_version_test.rb
new file mode 100644
index 000000000..74844f3af
--- /dev/null
+++ b/test/models/domain/domain_version_test.rb
@@ -0,0 +1,52 @@
+require 'test_helper'
+
+class DomainVersionTest < ActiveSupport::TestCase
+ def setup
+ super
+
+ @domain = domains(:shop)
+ @contacts = @domain.contacts
+ @user = users(:registrant)
+ end
+
+ def teardown
+ super
+ end
+
+ def test_assigns_creator_to_paper_trail_whodunnit
+ duplicate_domain = prepare_duplicate_domain
+
+ PaperTrail.whodunnit = @user.id_role_username
+ assert_difference 'duplicate_domain.versions.count', 1 do
+ duplicate_domain.save!
+ end
+
+ assert_equal(duplicate_domain.creator, @user)
+ assert_equal(duplicate_domain.updator, @user)
+ assert_equal(duplicate_domain.creator_str, @user.id_role_username)
+ assert_equal(duplicate_domain.updator_str, @user.id_role_username)
+ end
+
+ def test_assigns_updator_to_paper_trail_whodunnit
+ PaperTrail.whodunnit = @user.id_role_username
+
+ assert_difference '@domain.versions.count', 1 do
+ @domain.apply_registry_lock
+ end
+
+ assert_equal(@domain.updator, @user)
+ assert_equal(@domain.updator_str, @user.id_role_username)
+ end
+
+ private
+
+ def prepare_duplicate_domain
+ duplicate_domain = @domain.dup
+ duplicate_domain.tech_contacts << @contacts
+ duplicate_domain.admin_contacts << @contacts
+ duplicate_domain.name = 'duplicate.test'
+ duplicate_domain.uuid = nil
+
+ duplicate_domain
+ end
+end
diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb
index 706af1ef0..646a34015 100644
--- a/test/models/domain/force_delete_test.rb
+++ b/test/models/domain/force_delete_test.rb
@@ -1,7 +1,7 @@
require 'test_helper'
class DomainForceDeleteTest < ActiveSupport::TestCase
- def setup
+ setup do
@domain = domains(:shop)
end
@@ -74,7 +74,7 @@ class DomainForceDeleteTest < ActiveSupport::TestCase
end
def test_force_delete_cannot_be_scheduled_when_a_domain_is_discarded
- @domain.discard
+ @domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE])
assert_raises StandardError do
@domain.schedule_force_delete
end
diff --git a/test/models/domain/registry_lockable_test.rb b/test/models/domain/registry_lockable_test.rb
new file mode 100644
index 000000000..cadef69d2
--- /dev/null
+++ b/test/models/domain/registry_lockable_test.rb
@@ -0,0 +1,72 @@
+require 'test_helper'
+
+class DomainRegistryLockableTest < ActiveSupport::TestCase
+ def setup
+ super
+
+ @domain = domains(:airport)
+ end
+
+ def test_registry_lock_on_lockable_domain
+ refute(@domain.locked_by_registrant?)
+ @domain.apply_registry_lock
+
+ assert_equal(
+ [DomainStatus::SERVER_UPDATE_PROHIBITED,
+ DomainStatus::SERVER_DELETE_PROHIBITED,
+ DomainStatus::SERVER_TRANSFER_PROHIBITED],
+ @domain.statuses
+ )
+
+ assert(@domain.locked_by_registrant?)
+ assert(@domain.locked_by_registrant_at)
+ end
+
+ def test_registry_lock_cannot_be_applied_twice
+ @domain.apply_registry_lock
+ refute(@domain.apply_registry_lock)
+ assert(@domain.locked_by_registrant?)
+ assert(@domain.locked_by_registrant_at)
+ end
+
+ def test_registry_lock_cannot_be_applied_on_pending_statuses
+ @domain.statuses << DomainStatus::PENDING_RENEW
+ refute(@domain.apply_registry_lock)
+ refute(@domain.locked_by_registrant?)
+ refute(@domain.locked_by_registrant_at)
+ end
+
+ def test_remove_registry_lock_on_locked_domain
+ @domain.apply_registry_lock
+
+ assert_equal(
+ [DomainStatus::SERVER_UPDATE_PROHIBITED,
+ DomainStatus::SERVER_DELETE_PROHIBITED,
+ DomainStatus::SERVER_TRANSFER_PROHIBITED],
+ @domain.statuses
+ )
+
+ @domain.remove_registry_lock
+
+ assert_equal(["ok"], @domain.statuses)
+ refute(@domain.locked_by_registrant?)
+ refute(@domain.locked_by_registrant_at)
+ end
+
+ def test_remove_registry_lock_on_non_locked_domain
+ refute(@domain.locked_by_registrant?)
+ refute(@domain.remove_registry_lock)
+
+ assert_equal([], @domain.statuses)
+ refute(@domain.locked_by_registrant?)
+ refute(@domain.locked_by_registrant_at)
+ end
+
+ def test_registry_lock_cannot_be_removed_if_statuses_were_set_by_admin
+ @domain.statuses << DomainStatus::SERVER_UPDATE_PROHIBITED
+ @domain.statuses << DomainStatus::SERVER_DELETE_PROHIBITED
+ @domain.statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED
+
+ refute(@domain.remove_registry_lock)
+ end
+end
diff --git a/test/models/registrant_user/registrant_user_creation_test.rb b/test/models/registrant_user/registrant_user_creation_test.rb
new file mode 100644
index 000000000..fc5a32b4c
--- /dev/null
+++ b/test/models/registrant_user/registrant_user_creation_test.rb
@@ -0,0 +1,56 @@
+require 'test_helper'
+
+class RegistrantUserCreationTest < ActiveSupport::TestCase
+ def test_find_or_create_by_api_data_creates_a_user
+ user_data = {
+ ident: '37710100070',
+ first_name: 'JOHN',
+ last_name: 'SMITH'
+ }
+
+ RegistrantUser.find_or_create_by_api_data(user_data)
+
+ user = User.find_by(registrant_ident: 'EE-37710100070')
+ assert_equal('JOHN SMITH', user.username)
+ end
+
+ def test_find_or_create_by_api_data_creates_a_user_after_upcasing_input
+ user_data = {
+ ident: '37710100070',
+ first_name: 'John',
+ last_name: 'Smith'
+ }
+
+ RegistrantUser.find_or_create_by_api_data(user_data)
+
+ user = User.find_by(registrant_ident: 'EE-37710100070')
+ assert_equal('JOHN SMITH', user.username)
+ end
+
+ def test_find_or_create_by_mid_data_creates_a_user
+ user_data = OpenStruct.new(user_country: 'EE', user_id_code: '37710100070',
+ user_givenname: 'JOHN', user_surname: 'SMITH')
+
+ RegistrantUser.find_or_create_by_mid_data(user_data)
+ user = User.find_by(registrant_ident: 'EE-37710100070')
+ assert_equal('JOHN SMITH', user.username)
+ end
+
+ def test_find_or_create_by_idc_with_legacy_header_creates_a_user
+ header = '/C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37710100070/SN=SMITH/GN=JOHN/serialNumber=37710100070'
+
+ RegistrantUser.find_or_create_by_idc_data(header, RegistrantUser::ACCEPTED_ISSUER)
+
+ user = User.find_by(registrant_ident: 'EE-37710100070')
+ assert_equal('JOHN SMITH', user.username)
+ end
+
+ def test_find_or_create_by_idc_with_rfc2253_header_creates_a_user
+ header = 'serialNumber=37710100070,GN=JOHN,SN=SMITH,CN=SMITH\\,JOHN\\,37710100070,OU=authentication,O=ESTEID,C=EE'
+
+ RegistrantUser.find_or_create_by_idc_data(header, RegistrantUser::ACCEPTED_ISSUER)
+
+ user = User.find_by(registrant_ident: 'EE-37710100070')
+ assert_equal('JOHN SMITH', user.username)
+ end
+end
diff --git a/test/models/registrant_user_test.rb b/test/models/registrant_user_test.rb
index 86ab5591a..298d3a096 100644
--- a/test/models/registrant_user_test.rb
+++ b/test/models/registrant_user_test.rb
@@ -1,62 +1,38 @@
+require 'test_helper'
+
class RegistrantUserTest < ActiveSupport::TestCase
def setup
super
+
+ @user = users(:registrant)
end
def teardown
super
end
- def test_find_or_create_by_api_data_creates_a_user
- user_data = {
- ident: '37710100070',
- first_name: 'JOHN',
- last_name: 'SMITH'
- }
+ def test_domains_returns_an_list_of_distinct_domains_associated_with_a_specific_id_code
+ domain_names = @user.domains.pluck(:name)
+ assert_equal(4, domain_names.length)
- RegistrantUser.find_or_create_by_api_data(user_data)
-
- user = User.find_by(registrant_ident: 'EE-37710100070')
- assert_equal('JOHN SMITH', user.username)
+ # User is a registrant, but not a contact for the domain. Should be included in the list.
+ assert(domain_names.include?('shop.test'))
end
- def test_find_or_create_by_api_data_creates_a_user_after_upcasing_input
- user_data = {
- ident: '37710100070',
- first_name: 'John',
- last_name: 'Smith'
- }
+ def test_administered_domains_returns_a_list_of_domains
+ domain_names = @user.administered_domains.pluck(:name)
+ assert_equal(3, domain_names.length)
- RegistrantUser.find_or_create_by_api_data(user_data)
-
- user = User.find_by(registrant_ident: 'EE-37710100070')
- assert_equal('JOHN SMITH', user.username)
+ # User is a tech contact for the domain.
+ refute(domain_names.include?('library.test'))
end
- def test_find_or_create_by_mid_data_creates_a_user
- user_data = OpenStruct.new(user_country: 'EE', user_id_code: '37710100070',
- user_givenname: 'JOHN', user_surname: 'SMITH')
-
- RegistrantUser.find_or_create_by_mid_data(user_data)
- user = User.find_by(registrant_ident: 'EE-37710100070')
- assert_equal('JOHN SMITH', user.username)
+ def test_contacts_returns_an_list_of_contacts_associated_with_a_specific_id_code
+ assert_equal(1, @user.contacts.count)
end
- def test_find_or_create_by_idc_with_legacy_header_creates_a_user
- header = '/C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37710100070/SN=SMITH/GN=JOHN/serialNumber=37710100070'
-
- RegistrantUser.find_or_create_by_idc_data(header, RegistrantUser::ACCEPTED_ISSUER)
-
- user = User.find_by(registrant_ident: 'EE-37710100070')
- assert_equal('JOHN SMITH', user.username)
- end
-
- def test_find_or_create_by_idc_with_rfc2253_header_creates_a_user
- header = 'serialNumber=37710100070,GN=JOHN,SN=SMITH,CN=SMITH\\,JOHN\\,37710100070,OU=authentication,O=ESTEID,C=EE'
-
- RegistrantUser.find_or_create_by_idc_data(header, RegistrantUser::ACCEPTED_ISSUER)
-
- user = User.find_by(registrant_ident: 'EE-37710100070')
- assert_equal('JOHN SMITH', user.username)
+ def test_ident_and_country_code_helper_methods
+ assert_equal('1234', @user.ident)
+ assert_equal('US', @user.country_code)
end
end
diff --git a/test/system/admin_area/contact_versions_test.rb b/test/system/admin_area/contact_versions_test.rb
index 10d20615a..8130706b6 100644
--- a/test/system/admin_area/contact_versions_test.rb
+++ b/test/system/admin_area/contact_versions_test.rb
@@ -21,8 +21,8 @@ class ContactVersionsTest < ApplicationSystemTestCase
VALUES (75, 'test_registrar', 'test123', 'test@test.com', 'EE', 'TEST123',
'test123', 'en');
- INSERT INTO contacts (id, code, auth_info, registrar_id)
- VALUES (75, 'test_code', '8b4d462aa04194ca78840a', 75);
+ INSERT INTO contacts (id, code, email, auth_info, registrar_id)
+ VALUES (75, 'test_code', 'test@inbox.test', '8b4d462aa04194ca78840a', 75);
INSERT INTO log_contacts (item_type, item_id, event, whodunnit, object,
object_changes, created_at, session, children, ident_updated_at, uuid)
diff --git a/test/system/admin_area/domain_versions_test.rb b/test/system/admin_area/domain_versions_test.rb
index 6c375cefe..dae7592c8 100644
--- a/test/system/admin_area/domain_versions_test.rb
+++ b/test/system/admin_area/domain_versions_test.rb
@@ -21,8 +21,8 @@ class DomainVersionsTest < ApplicationSystemTestCase
VALUES (54, 'test_registrar', 'test123', 'test@test.com', 'EE', 'TEST123',
'test123', 'en');
- INSERT INTO contacts (id, code, auth_info, registrar_id)
- VALUES (54, 'test_code', '8b4d462aa04194ca78840a', 54);
+ INSERT INTO contacts (id, code, email, auth_info, registrar_id)
+ VALUES (54, 'test_code', 'test@inbox.test', '8b4d462aa04194ca78840a', 54);
INSERT INTO domains (id, registrar_id, valid_to, registrant_id,
transfer_code)
diff --git a/test/system/admin_area/domains/details_test.rb b/test/system/admin_area/domains/details_test.rb
index 31a46a19b..04cdb06e3 100644
--- a/test/system/admin_area/domains/details_test.rb
+++ b/test/system/admin_area/domains/details_test.rb
@@ -7,9 +7,14 @@ class AdminAreaDomainDetailsTest < ApplicationSystemTestCase
end
def test_discarded_domain_has_corresponding_label
+ travel_to Time.zone.parse('2010-07-05 10:30')
+ @domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+
visit admin_domain_url(@domain)
assert_no_css 'span.label.label-warning', text: 'deleteCandidate'
+
@domain.discard
+
visit admin_domain_url(@domain)
assert_css 'span.label.label-warning', text: 'deleteCandidate'
end
diff --git a/test/system/admin_area/domains/force_delete_test.rb b/test/system/admin_area/domains/force_delete_test.rb
index 22bb23d38..75835cf59 100644
--- a/test/system/admin_area/domains/force_delete_test.rb
+++ b/test/system/admin_area/domains/force_delete_test.rb
@@ -56,7 +56,7 @@ class AdminAreaDomainForceDeleteTest < ApplicationSystemTestCase
end
def test_force_delete_procedure_cannot_be_scheduled_on_a_discarded_domain
- @domain.discard
+ @domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE])
visit edit_admin_domain_url(@domain)
assert_no_button 'Schedule force delete'
diff --git a/test/system/admin_area/domains/registry_lock_test.rb b/test/system/admin_area/domains/registry_lock_test.rb
new file mode 100644
index 000000000..ce09c445c
--- /dev/null
+++ b/test/system/admin_area/domains/registry_lock_test.rb
@@ -0,0 +1,57 @@
+require 'test_helper'
+
+class AdminAreaRegistryLockTest < JavaScriptApplicationSystemTestCase
+ def setup
+ super
+ WebMock.allow_net_connect!
+
+ sign_in users(:admin)
+ travel_to Time.zone.parse('2010-07-05 00:30:00')
+ @domain = domains(:airport)
+ end
+
+ def teardown
+ travel_back
+ end
+
+ def test_does_not_have_link_when_domain_is_not_locked
+ visit edit_admin_domain_path(@domain)
+ click_link_or_button('Actions')
+ refute(page.has_link?('Remove registry lock'))
+ end
+
+ def test_can_remove_registry_lock_from_a_domain
+ @domain.apply_registry_lock
+
+ visit edit_admin_domain_path(@domain)
+ click_link_or_button('Actions')
+ assert(page.has_link?('Remove registry lock'))
+
+ accept_confirm('Are you sure you want to remove the registry lock?') do
+ click_link_or_button('Remove registry lock')
+ end
+
+ assert_text('Registry lock removed')
+
+ @domain.reload
+ refute @domain.locked_by_registrant?
+ end
+
+ def test_cannot_remove_registry_lock_from_not_locked_domain
+ @domain.apply_registry_lock
+ visit edit_admin_domain_path(@domain)
+ @domain.remove_registry_lock
+
+ refute @domain.locked_by_registrant?
+
+ click_link_or_button('Actions')
+ assert(page.has_link?('Remove registry lock'))
+
+ accept_confirm('Are you sure you want to remove the registry lock?') do
+ click_link_or_button('Remove registry lock')
+ end
+
+ assert_text('Registry lock could not be removed')
+ refute @domain.locked_by_registrant?
+ end
+end
diff --git a/test/system/admin_area/domains_test.rb b/test/system/admin_area/domains_test.rb
index 538de2604..91e484fc7 100644
--- a/test/system/admin_area/domains_test.rb
+++ b/test/system/admin_area/domains_test.rb
@@ -3,11 +3,41 @@ require 'test_helper'
class AdminDomainsTestTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
+ travel_to Time.zone.parse('2010-07-05 00:30:00')
+ @domain = domains(:shop)
+ end
+
+ teardown do
+ travel_back
end
def test_shows_details
- domain = domains(:shop)
- visit admin_domain_path(domain)
- assert_field nil, with: domain.transfer_code
+ visit admin_domain_path(@domain)
+ assert_field nil, with: @domain.transfer_code
+ end
+
+ def test_admin_registry_lock_date
+ visit admin_domain_path(@domain)
+ refute_text 'Registry lock time 2010-07-05 00:30'
+
+ lockable_domain = domains(:airport)
+ lockable_domain.apply_registry_lock
+
+ visit admin_domain_path(lockable_domain)
+ assert_text 'Registry lock time 2010-07-05 00:30'
+ assert_text 'registryLock'
+ end
+
+ def test_keep_a_domain
+ @domain.delete_at = Time.zone.parse('2010-07-05 10:00')
+ @domain.discard
+
+ visit edit_admin_domain_url(@domain)
+ click_link_or_button 'Remove deleteCandidate status'
+ @domain.reload
+
+ assert_not @domain.discarded?
+ assert_text 'deleteCandidate status has been removed'
+ assert_no_link 'Remove deleteCandidate status'
end
end