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/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/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 a7ba227e6..50ab62242 100644
--- a/app/models/domain.rb
+++ b/app/models/domain.rb
@@ -7,6 +7,7 @@ class Domain < ActiveRecord::Base
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 }
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 03516af5e..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
@@ -59,20 +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: 'btn btn-default'
+ 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',
@@ -86,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 5bbb9f241..2317bc330 100644
--- a/app/views/admin/domains/edit.html.erb
+++ b/app/views/admin/domains/edit.html.erb
@@ -5,19 +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 41751874a..7773331c8 100644
--- a/config/locales/admin/domains.en.yml
+++ b/config/locales/admin/domains.en.yml
@@ -13,7 +13,7 @@ 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?
@@ -28,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
@@ -38,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
@@ -53,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 362271516..f173ccad4 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
@@ -195,6 +197,7 @@ Rails.application.routes.draw do
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
@@ -265,4 +268,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/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/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 c0b4742e6..b93136de2 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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
);
@@ -4762,6 +4763,8 @@ 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 ('20180823161237');
@@ -4772,6 +4775,8 @@ INSERT INTO schema_migrations (version) VALUES ('20180823174331');
INSERT INTO schema_migrations (version) VALUES ('20180823212823');
+INSERT INTO schema_migrations (version) VALUES ('20180824092855');
+
INSERT INTO schema_migrations (version) VALUES ('20180824102834');
INSERT INTO schema_migrations (version) VALUES ('20180825153657');
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/spec/models/domain_spec.rb b/spec/models/domain_spec.rb
index c88e25a6f..075f9ea02 100644
--- a/spec/models/domain_spec.rb
+++ b/spec/models/domain_spec.rb
@@ -299,50 +299,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
diff --git a/test/fixtures/contacts.yml b/test/fixtures/contacts.yml
index cb9387090..06bd2c90e 100644
--- a/test/fixtures/contacts.yml
+++ b/test/fixtures/contacts.yml
@@ -15,7 +15,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
@@ -34,7 +34,7 @@ jane:
name: Jane
email: jane@mail.test
phone: '+555.555'
- ident: 1234
+ ident: 123456
ident_type: priv
ident_country_code: US
registrar: bestnames
@@ -46,7 +46,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
@@ -58,7 +58,7 @@ jack:
name: Jack
email: jack@inbox.test
phone: '+555.555'
- ident: 1234
+ ident: 12345678
ident_type: org
registrar: goodnames
ident_country_code: US
@@ -87,4 +87,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_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb
index ddeaee9f3..97f0c8886 100644
--- a/test/integration/api/registrant/registrant_api_contacts_test.rb
+++ b/test/integration/api/registrant/registrant_api_contacts_test.rb
@@ -25,7 +25,7 @@ class RegistrantApiContactsTest < ApplicationIntegrationTest
assert_equal(200, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
- assert_equal(5, json_body.count)
+ assert_equal(4, json_body.count)
array_of_contact_codes = json_body.map { |x| x[:code] }
assert(array_of_contact_codes.include?('william-001'))
assert(array_of_contact_codes.include?('jane-001'))
@@ -39,7 +39,7 @@ class RegistrantApiContactsTest < ApplicationIntegrationTest
get '/api/v1/registrant/contacts', {}, @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
- assert_equal(5, response_json.count)
+ assert_equal(4, response_json.count)
end
def test_get_contact_details_by_uuid
diff --git a/test/integration/api/registrant/registrant_api_domains_test.rb b/test/integration/api/registrant/registrant_api_domains_test.rb
index 128d15e20..0764db3aa 100644
--- a/test/integration/api/registrant/registrant_api_domains_test.rb
+++ b/test/integration/api/registrant/registrant_api_domains_test.rb
@@ -57,7 +57,7 @@ class RegistrantApiDomainsTest < ApplicationIntegrationTest
get '/api/v1/registrant/domains', {}, @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
- assert_equal(5, response_json.count)
+ assert_equal(4, response_json.count)
end
def test_root_does_not_accept_limit_higher_than_200
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/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/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/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 cd7a8f10a..91e484fc7 100644
--- a/test/system/admin_area/domains_test.rb
+++ b/test/system/admin_area/domains_test.rb
@@ -3,6 +3,7 @@ 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
@@ -15,8 +16,19 @@ class AdminDomainsTestTest < ApplicationSystemTestCase
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
- travel_to Time.zone.parse('2010-07-05 10:30')
@domain.delete_at = Time.zone.parse('2010-07-05 10:00')
@domain.discard