diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 8a5c275c5..1ebf2946d 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -27,12 +27,25 @@ module Admin def casted_settings settings = {} - + params[:settings].each do |k, v| - settings[k] = { value: v } + setting = SettingEntry.find(k) + value = if setting.format == 'array' + processed_hash = available_options.each_with_object({}) do |option, hash| + hash[option] = (v[option] == "true") + end + processed_hash.to_json + else + v + end + settings[k] = { value: value } end - + settings end + + def available_options + %w[birthday priv org] + end end end diff --git a/app/models/domain.rb b/app/models/domain.rb index 1d1a13083..389d361ba 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -186,6 +186,8 @@ class Domain < ApplicationRecord object_count: admin_contacts_validation_rules(for_org: false), unless: :require_admin_contacts? + validate :validate_admin_contacts_ident_type, on: :create + validates :tech_domain_contacts, object_count: tech_contacts_validation_rules(for_org: true), if: :require_tech_contacts? @@ -857,10 +859,10 @@ class Domain < ApplicationRecord end def require_admin_contacts? - return true if registrant.org? + return true if registrant.org? && Setting.admin_contacts_required_for_org return false unless registrant.priv? - underage_registrant? + underage_registrant? && Setting.admin_contacts_required_for_minors end def require_tech_contacts? @@ -916,4 +918,18 @@ class Domain < ApplicationRecord Date.parse("#{birth_year}-#{month}-#{day}") end + + def validate_admin_contacts_ident_type + allowed_types = Setting.admin_contacts_allowed_ident_type + return if allowed_types.blank? + + admin_contacts.each do |contact| + next if allowed_types[contact.ident_type] == true + + errors.add(:admin_contacts, I18n.t( + 'activerecord.errors.models.domain.admin_contact_invalid_ident_type', + ident_type: contact.ident_type + )) + end + end end diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb index ab5d0c16e..f56947c07 100644 --- a/app/models/epp/domain.rb +++ b/app/models/epp/domain.rb @@ -36,6 +36,23 @@ class Epp::Domain < Domain ok = false end end + + # Validate admin contacts ident type only for new domains or new admin contacts + allowed_types = Setting.admin_contacts_allowed_ident_type + if allowed_types.present? + active_admins.each do |admin_contact| + next if !new_record? && admin_contact.persisted? && !admin_contact.changed? + + contact = admin_contact.contact + unless allowed_types[contact.ident_type] == true + add_epp_error('2306', 'contact', contact.code, + I18n.t('activerecord.errors.models.domain.admin_contact_invalid_ident_type', + ident_type: contact.ident_type)) + ok = false + end + end + end + ok end @@ -95,7 +112,8 @@ class Epp::Domain < Domain [:base, :key_data_not_allowed], [:period, :not_a_number], [:period, :not_an_integer], - [:registrant, :cannot_be_missing] + [:registrant, :cannot_be_missing], + [:admin_contacts, :invalid_ident_type] ], '2308' => [ [:base, :domain_name_blocked, { value: { obj: 'name', val: name_dirty } }], @@ -414,15 +432,15 @@ class Epp::Domain < Domain end def require_admin_contacts? - return true if registrant.org? + return true if registrant.org? && Setting.admin_contacts_required_for_org return false unless registrant.priv? - underage_registrant? + underage_registrant? && Setting.admin_contacts_required_for_minors end def tech_contacts_validation_rules(for_org:) { - min: 0, # Технический контакт опционален для всех + min: 0, max: -> { Setting.tech_contacts_max_count } } end diff --git a/app/models/setting_entry.rb b/app/models/setting_entry.rb index 966831881..739ec71fb 100644 --- a/app/models/setting_entry.rb +++ b/app/models/setting_entry.rb @@ -87,6 +87,17 @@ class SettingEntry < ApplicationRecord end def array_format - JSON.parse(value).to_a + begin + if value.is_a?(String) + JSON.parse(value) + elsif value.is_a?(Hash) + value + else + { 'birthday' => true, 'priv' => true, 'org' => true } + end + rescue JSON::ParserError => e + puts "JSON Parse error: #{e.message}" + { 'birthday' => true, 'priv' => true, 'org' => true } + end end end diff --git a/app/views/admin/settings/_setting_row.haml b/app/views/admin/settings/_setting_row.haml index 44078f32d..720ab6199 100644 --- a/app/views/admin/settings/_setting_row.haml +++ b/app/views/admin/settings/_setting_row.haml @@ -1,6 +1,32 @@ %tr{class: (@errors && @errors.has_key?(setting.code) && "danger")} %td.col-md-6= setting.code.humanize - - if [TrueClass, FalseClass].include?(setting.retrieve.class) + - if setting.format == 'array' + %td.col-md-6 + - available_options = ['birthday', 'priv', 'org'] + - begin + - raw_value = setting.retrieve + - current_values = if raw_value.is_a?(Hash) + - raw_value + - elsif raw_value.is_a?(Array) && raw_value.first.is_a?(Array) + - Hash[raw_value] + - elsif raw_value.is_a?(Array) + - available_options.each_with_object({}) { |opt, hash| hash[opt] = raw_value.include?(opt) } + - else + - begin + - parsed = JSON.parse(raw_value.to_s) + - parsed.is_a?(Hash) ? parsed : available_options.each_with_object({}) { |opt, hash| hash[opt] = true } + - rescue => e + - available_options.each_with_object({}) { |opt, hash| hash[opt] = true } + .row + - available_options.each do |option| + .col-md-4 + .checkbox + %label + = check_box_tag "settings[#{setting.id}][#{option}]", "true", current_values[option], + id: "setting_#{setting.id}_#{option}", + data: { value: current_values[option] } + = option.humanize + - elsif [TrueClass, FalseClass].include?(setting.retrieve.class) %td.col-md-6 = hidden_field_tag("[settings][#{setting.id}]", '', id: nil) = check_box_tag("[settings][#{setting.id}]", true, setting.retrieve) diff --git a/config/locales/en.yml b/config/locales/en.yml index 2f9ba4b15..4083747fe 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -81,6 +81,7 @@ en: domain: <<: *epp_domain_ar_attributes + admin_contact_invalid_ident_type: "Administrative contact with identification type '%{ident_type}' is not allowed" nameserver: attributes: diff --git a/db/seeds.rb b/db/seeds.rb index 4cb36e738..73e8e6f4e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,6 +7,19 @@ ActiveRecord::Base.transaction do SettingEntry.create(code: 'directo_sales_agent', value: 'HELEN', format: 'string', group: 'billing') SettingEntry.create(code: 'admin_contacts_min_count', value: '1', format: 'integer', group: 'domain_validation') SettingEntry.create(code: 'admin_contacts_max_count', value: '10', format: 'integer', group: 'domain_validation') + SettingEntry.create(code: 'admin_contacts_required_for_org', value: 'true', format: 'boolean', group: 'domain_validation') + SettingEntry.create(code: 'admin_contacts_required_for_minors', value: 'true', format: 'boolean', group: 'domain_validation') + SettingEntry.create( + code: 'admin_contacts_allowed_ident_type', + value: { + 'birthday' => true, + 'priv' => true, + 'org' => true + }.to_json, + format: 'array', + group: 'domain_validation' + ) + SettingEntry.create(code: 'tech_contacts_min_count', value: '1', format: 'integer', group: 'domain_validation') SettingEntry.create(code: 'tech_contacts_max_count', value: '10', format: 'integer', group: 'domain_validation') SettingEntry.create(code: 'orphans_contacts_in_months', value: '6', format: 'integer', group: 'domain_validation') diff --git a/test/fixtures/setting_entries.yml b/test/fixtures/setting_entries.yml index c780f6f2a..2df870258 100644 --- a/test/fixtures/setting_entries.yml +++ b/test/fixtures/setting_entries.yml @@ -469,3 +469,27 @@ ip_whitelist_max_count: format: integer created_at: <%= Time.zone.parse('2010-07-05') %> updated_at: <%= Time.zone.parse('2010-07-05') %> + +admin_contacts_required_for_org: + code: admin_contacts_required_for_org + value: 'true' + group: domain_validation + format: boolean + created_at: <%= Time.zone.parse('2010-07-05') %> + updated_at: <%= Time.zone.parse('2010-07-05') %> + +admin_contacts_required_for_minors: + code: admin_contacts_required_for_minors + value: 'true' + group: domain_validation + format: boolean + created_at: <%= Time.zone.parse('2010-07-05') %> + updated_at: <%= Time.zone.parse('2010-07-05') %> + +admin_contacts_allowed_ident_type: + code: admin_contacts_allowed_ident_type + value: '{"birthday":true,"priv":true,"org":false}' + group: domain_validation + format: array + created_at: <%= Time.zone.parse('2010-07-05') %> + updated_at: <%= Time.zone.parse('2010-07-05') %> diff --git a/test/integration/epp/domain/create/base_test.rb b/test/integration/epp/domain/create/base_test.rb index 12eb5bb03..dbb2566ba 100644 --- a/test/integration/epp/domain/create/base_test.rb +++ b/test/integration/epp/domain/create/base_test.rb @@ -597,6 +597,8 @@ class EppDomainCreateBaseTest < EppTestCase end def test_registers_new_domain_with_required_attributes + Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true } + now = Time.zone.parse('2010-07-05') travel_to now name = "new.#{dns_zones(:one).origin}" @@ -644,6 +646,8 @@ class EppDomainCreateBaseTest < EppTestCase default_registration_period = 1.year + 1.day assert_equal now + default_registration_period, domain.expire_time + + Setting.admin_contacts_allowed_ident_type = { 'org' => false, 'priv' => true, 'birthday' => true } end def test_registers_domain_without_legaldoc_if_optout @@ -1108,4 +1112,78 @@ class EppDomainCreateBaseTest < EppTestCase ENV["shunter_default_threshold"] = '10000' ENV["shunter_enabled"] = 'false' end + + def test_does_not_register_domain_with_invalid_admin_contact_ident_type + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + admin_contact = contacts(:william) + admin_contact.update!(ident_type: 'org') + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{admin_contact.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_no_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + response_xml = Nokogiri::XML(response.body) + assert_correct_against_schema response_xml + assert_epp_response :parameter_value_policy_error + end + + def test_registers_domain_with_valid_admin_contact_ident_type + name = "new.#{dns_zones(:one).origin}" + contact = contacts(:john) + registrant = contact.becomes(Registrant) + admin_contact = contacts(:william) + admin_contact.update!(ident_type: 'priv') + + request_xml = <<-XML + + + + + + #{name} + #{registrant.code} + #{admin_contact.code} + + + + + #{'test' * 2000} + + + + + XML + + assert_difference 'Domain.count' do + post epp_create_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end + + response_xml = Nokogiri::XML(response.body) + assert_correct_against_schema response_xml + assert_epp_response :completed_successfully + end end diff --git a/test/integration/epp/domain/update/base_test.rb b/test/integration/epp/domain/update/base_test.rb index 4f11b1145..0c678afea 100644 --- a/test/integration/epp/domain/update/base_test.rb +++ b/test/integration/epp/domain/update/base_test.rb @@ -969,6 +969,98 @@ class EppDomainUpdateBaseTest < EppTestCase ENV["shunter_enabled"] = 'false' end + def test_does_not_update_domain_with_invalid_admin_contact_ident_type + admin_contact = contacts(:william) + admin_contact.update!(ident_type: 'org') + + request_xml = <<-XML + + + + + + shop.test + + #{admin_contact.code} + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_correct_against_schema response_xml + assert_epp_response :parameter_value_policy_error + end + + def test_updates_domain_with_valid_admin_contact_ident_type + admin_contact = contacts(:william) + admin_contact.update!(ident_type: 'priv') + + request_xml = <<-XML + + + + + + shop.test + + #{admin_contact.code} + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_correct_against_schema response_xml + assert_epp_response :completed_successfully + end + + def test_skips_admin_contact_ident_type_validation_for_existing_contacts + admin_contact = contacts(:william) + admin_contact.update!(ident_type: 'org') + @domain.admin_contacts << admin_contact + @domain.save! + + # Change allowed types after domain is created + Setting.admin_contacts_allowed_ident_type = { 'birthday' => true, 'priv' => true, 'org' => false } + + # Try to update domain with some other changes + request_xml = <<-XML + + + + + + shop.test + + + new_pw + + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + + response_xml = Nokogiri::XML(response.body) + assert_correct_against_schema response_xml + assert_epp_response :completed_successfully + end + private def assert_verification_and_notification_emails diff --git a/test/models/domain/domain_version_test.rb b/test/models/domain/domain_version_test.rb index 4b937e274..7cfdae8d9 100644 --- a/test/models/domain/domain_version_test.rb +++ b/test/models/domain/domain_version_test.rb @@ -14,6 +14,7 @@ class DomainVersionTest < ActiveSupport::TestCase end def test_assigns_creator_to_paper_trail_whodunnit + Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true } duplicate_domain = prepare_duplicate_domain PaperTrail.request.whodunnit = @user.id_role_username diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb index d3efe684f..af25c535c 100644 --- a/test/models/domain_test.rb +++ b/test/models/domain_test.rb @@ -9,6 +9,9 @@ class DomainTest < ActiveSupport::TestCase @original_max_admin_contact_count = Setting.admin_contacts_max_count @original_min_tech_contact_count = Setting.tech_contacts_min_count @original_max_tech_contact_count = Setting.tech_contacts_max_count + @original_admin_contacts_required_for_org = Setting.admin_contacts_required_for_org + @original_admin_contacts_required_for_minors = Setting.admin_contacts_required_for_minors + @original_admin_contacts_allowed_ident_type = Setting.admin_contacts_allowed_ident_type end teardown do @@ -17,6 +20,9 @@ class DomainTest < ActiveSupport::TestCase Setting.admin_contacts_max_count = @original_max_admin_contact_count Setting.tech_contacts_min_count = @original_min_tech_contact_count Setting.tech_contacts_max_count = @original_max_tech_contact_count + Setting.admin_contacts_required_for_org = @original_admin_contacts_required_for_org + Setting.admin_contacts_required_for_minors = @original_admin_contacts_required_for_minors + Setting.admin_contacts_allowed_ident_type = @original_admin_contacts_allowed_ident_type end def test_valid_domain_is_valid @@ -615,6 +621,42 @@ class DomainTest < ActiveSupport::TestCase assert domain.valid? end + def test_validates_admin_contact_required_for_org_based_on_setting + domain = valid_domain + domain.registrant.update!(ident_type: 'org') + domain.reload + + # When setting is true + Setting.admin_contacts_required_for_org = true + domain.admin_domain_contacts.clear + assert domain.invalid? + assert_includes domain.errors.full_messages, + 'Admin domain contacts Admin contacts count must be between 1-10' + + # When setting is false + Setting.admin_contacts_required_for_org = false + domain.admin_domain_contacts.clear + assert domain.valid? + end + + def test_validates_admin_contact_required_for_minors_based_on_setting + domain = valid_domain + domain.registrant.update!(ident_type: 'birthday', ident: '2010-07-05') + domain.reload + + # When setting is true + Setting.admin_contacts_required_for_minors = true + domain.admin_domain_contacts.clear + assert domain.invalid? + assert_includes domain.errors.full_messages, + 'Admin domain contacts Admin contacts count must be between 1-10' + + # When setting is false + Setting.admin_contacts_required_for_minors = false + domain.admin_domain_contacts.clear + assert domain.valid? + end + private def valid_domain