mirror of
https://github.com/internetee/registry.git
synced 2025-07-31 15:06:23 +02:00
Merge pull request #2770 from internetee/2750-admin-contact-cannot-be-a-minor-clean
2750 admin contact cannot be a minor clean
This commit is contained in:
commit
5946afc729
12 changed files with 189 additions and 52 deletions
|
@ -1,4 +1,8 @@
|
||||||
class AdminDomainContact < DomainContact
|
class AdminDomainContact < DomainContact
|
||||||
|
include AgeValidation
|
||||||
|
|
||||||
|
validate :validate_contact_age
|
||||||
|
|
||||||
# rubocop:disable Metrics/AbcSize
|
# rubocop:disable Metrics/AbcSize
|
||||||
# rubocop:disable Metrics/MethodLength
|
# rubocop:disable Metrics/MethodLength
|
||||||
def self.replace(current_contact, new_contact)
|
def self.replace(current_contact, new_contact)
|
||||||
|
@ -23,4 +27,14 @@ class AdminDomainContact < DomainContact
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/AbcSize
|
# rubocop:enable Metrics/AbcSize
|
||||||
# rubocop:enable Metrics/MethodLength
|
# rubocop:enable Metrics/MethodLength
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_contact_age
|
||||||
|
return unless contact&.underage?
|
||||||
|
|
||||||
|
errors.add(:contact, I18n.t(
|
||||||
|
'activerecord.errors.models.admin_domain_contact.contact_too_young'
|
||||||
|
))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
53
app/models/concerns/age_validation.rb
Normal file
53
app/models/concerns/age_validation.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
module AgeValidation
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def underage?
|
||||||
|
case ident_type
|
||||||
|
when 'birthday'
|
||||||
|
underage_by_birthday?
|
||||||
|
when 'priv'
|
||||||
|
underage_by_estonian_id?
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def underage_by_birthday?
|
||||||
|
birth_date = Date.parse(ident)
|
||||||
|
calculate_age(birth_date) < 18
|
||||||
|
end
|
||||||
|
|
||||||
|
def underage_by_estonian_id?
|
||||||
|
return false unless estonian_id?
|
||||||
|
|
||||||
|
birth_date = parse_estonian_id_birth_date(ident)
|
||||||
|
calculate_age(birth_date) < 18
|
||||||
|
end
|
||||||
|
|
||||||
|
def estonian_id?
|
||||||
|
ident_country_code == 'EE' && ident.match?(/^\d{11}$/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def calculate_age(birth_date)
|
||||||
|
((Time.zone.now - birth_date.to_time) / 1.year.seconds).floor
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_estonian_id_birth_date(id_code)
|
||||||
|
century_number = id_code[0].to_i
|
||||||
|
year_digits = id_code[1..2]
|
||||||
|
month = id_code[3..4]
|
||||||
|
day = id_code[5..6]
|
||||||
|
|
||||||
|
birth_year = case century_number
|
||||||
|
when 1, 2 then "18#{year_digits}"
|
||||||
|
when 3, 4 then "19#{year_digits}"
|
||||||
|
when 5, 6 then "20#{year_digits}"
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Invalid century number in Estonian ID"
|
||||||
|
end
|
||||||
|
|
||||||
|
Date.parse("#{birth_year}-#{month}-#{day}")
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,6 +10,7 @@ class Contact < ApplicationRecord
|
||||||
include Contact::Archivable
|
include Contact::Archivable
|
||||||
include Contact::CompanyRegister
|
include Contact::CompanyRegister
|
||||||
include EmailVerifable
|
include EmailVerifable
|
||||||
|
include AgeValidation
|
||||||
|
|
||||||
belongs_to :original, class_name: 'Contact'
|
belongs_to :original, class_name: 'Contact'
|
||||||
belongs_to :registrar, required: true
|
belongs_to :registrar, required: true
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Domain < ApplicationRecord
|
||||||
include Domain::Releasable
|
include Domain::Releasable
|
||||||
include Domain::Disputable
|
include Domain::Disputable
|
||||||
include Domain::BulkUpdatable
|
include Domain::BulkUpdatable
|
||||||
|
include AgeValidation
|
||||||
|
|
||||||
PERIODS = [
|
PERIODS = [
|
||||||
['3 months', '3m'],
|
['3 months', '3m'],
|
||||||
|
@ -866,7 +867,7 @@ class Domain < ApplicationRecord
|
||||||
return true if registrant.org? && Setting.admin_contacts_required_for_org
|
return true if registrant.org? && Setting.admin_contacts_required_for_org
|
||||||
return false unless registrant.priv?
|
return false unless registrant.priv?
|
||||||
|
|
||||||
underage_registrant? && Setting.admin_contacts_required_for_minors
|
registrant.underage? && Setting.admin_contacts_required_for_minors
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_tech_contacts?
|
def require_tech_contacts?
|
||||||
|
@ -876,51 +877,7 @@ class Domain < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def underage_registrant?
|
def underage_registrant?
|
||||||
case registrant.ident_type
|
registrant.underage?
|
||||||
when 'birthday'
|
|
||||||
underage_by_birthday?
|
|
||||||
when 'priv'
|
|
||||||
underage_by_estonian_id?
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def underage_by_birthday?
|
|
||||||
birth_date = Date.parse(registrant.ident)
|
|
||||||
calculate_age(birth_date) < 18
|
|
||||||
end
|
|
||||||
|
|
||||||
def underage_by_estonian_id?
|
|
||||||
return false unless estonian_id?
|
|
||||||
|
|
||||||
birth_date = parse_estonian_id_birth_date(registrant.ident)
|
|
||||||
calculate_age(birth_date) < 18
|
|
||||||
end
|
|
||||||
|
|
||||||
def estonian_id?
|
|
||||||
registrant.ident_country_code == 'EE' && registrant.ident.match?(/^\d{11}$/)
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_age(birth_date)
|
|
||||||
((Time.zone.now - birth_date.to_time) / 1.year.seconds).floor
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_estonian_id_birth_date(id_code)
|
|
||||||
century_number = id_code[0].to_i
|
|
||||||
year_digits = id_code[1..2]
|
|
||||||
month = id_code[3..4]
|
|
||||||
day = id_code[5..6]
|
|
||||||
|
|
||||||
birth_year = case century_number
|
|
||||||
when 1, 2 then "18#{year_digits}"
|
|
||||||
when 3, 4 then "19#{year_digits}"
|
|
||||||
when 5, 6 then "20#{year_digits}"
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Invalid century number in Estonian ID"
|
|
||||||
end
|
|
||||||
|
|
||||||
Date.parse("#{birth_year}-#{month}-#{day}")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_admin_contacts_ident_type
|
def validate_admin_contacts_ident_type
|
||||||
|
|
|
@ -37,7 +37,12 @@ class Epp::Domain < Domain
|
||||||
# validate registrant here as well
|
# validate registrant here as well
|
||||||
([Contact.find(registrant.id)] + active_admins + active_techs).each do |x|
|
([Contact.find(registrant.id)] + active_admins + active_techs).each do |x|
|
||||||
unless x.valid?
|
unless x.valid?
|
||||||
add_epp_error('2304', nil, nil, I18n.t(:contact_is_not_valid, value: x.try(:code)))
|
if x.class.name == 'AdminDomainContact' && x.contact.underage?
|
||||||
|
add_epp_error('2304', nil, nil, I18n.t('activerecord.errors.models.admin_domain_contact.contact_too_young'))
|
||||||
|
else
|
||||||
|
add_epp_error('2304', nil, nil, I18n.t(:contact_is_not_valid, value: x.try(:code)))
|
||||||
|
end
|
||||||
|
|
||||||
ok = false
|
ok = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -160,6 +160,8 @@ en:
|
||||||
ipv6:
|
ipv6:
|
||||||
taken: 'has already been taken'
|
taken: 'has already been taken'
|
||||||
|
|
||||||
|
admin_domain_contact:
|
||||||
|
contact_too_young: "Administrative contact must be at least 18 years old"
|
||||||
|
|
||||||
attributes:
|
attributes:
|
||||||
epp_domain: &epp_domain_attributes
|
epp_domain: &epp_domain_attributes
|
||||||
|
|
|
@ -23,11 +23,13 @@ class AdminAreaAdminUsersIntegrationTest < JavaScriptApplicationSystemTestCase
|
||||||
createNewAdminUser(true)
|
createNewAdminUser(true)
|
||||||
|
|
||||||
visit admin_admin_users_path
|
visit admin_admin_users_path
|
||||||
|
assert_selector 'a', text: 'test_user_name'
|
||||||
click_on 'test_user_name'
|
click_on 'test_user_name'
|
||||||
|
|
||||||
assert_text 'General'
|
assert_text 'General'
|
||||||
click_on 'Edit'
|
click_on 'Edit'
|
||||||
|
|
||||||
|
assert_selector 'input[name*="password"]'
|
||||||
fill_in 'Password', with: 'test_password'
|
fill_in 'Password', with: 'test_password'
|
||||||
fill_in 'Password confirmation', with: 'test_password'
|
fill_in 'Password confirmation', with: 'test_password'
|
||||||
|
|
||||||
|
|
|
@ -597,7 +597,7 @@ class EppDomainCreateBaseTest < EppTestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_registers_new_domain_with_required_attributes
|
def test_registers_new_domain_with_required_attributes
|
||||||
Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true }
|
Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true }.to_json
|
||||||
|
|
||||||
now = Time.zone.parse('2010-07-05')
|
now = Time.zone.parse('2010-07-05')
|
||||||
travel_to now
|
travel_to now
|
||||||
|
@ -647,7 +647,7 @@ class EppDomainCreateBaseTest < EppTestCase
|
||||||
default_registration_period = 1.year + 1.day
|
default_registration_period = 1.year + 1.day
|
||||||
assert_equal now + default_registration_period, domain.expire_time
|
assert_equal now + default_registration_period, domain.expire_time
|
||||||
|
|
||||||
Setting.admin_contacts_allowed_ident_type = { 'org' => false, 'priv' => true, 'birthday' => true }
|
Setting.admin_contacts_allowed_ident_type = { 'org' => false, 'priv' => true, 'birthday' => true }.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_registers_domain_without_legaldoc_if_optout
|
def test_registers_domain_without_legaldoc_if_optout
|
||||||
|
|
|
@ -1032,7 +1032,7 @@ class EppDomainUpdateBaseTest < EppTestCase
|
||||||
@domain.save!
|
@domain.save!
|
||||||
|
|
||||||
# Change allowed types after domain is created
|
# Change allowed types after domain is created
|
||||||
Setting.admin_contacts_allowed_ident_type = { 'birthday' => true, 'priv' => true, 'org' => false }
|
Setting.admin_contacts_allowed_ident_type = { 'birthday' => true, 'priv' => true, 'org' => false }.to_json
|
||||||
|
|
||||||
# Try to update domain with some other changes
|
# Try to update domain with some other changes
|
||||||
request_xml = <<-XML
|
request_xml = <<-XML
|
||||||
|
@ -1061,6 +1061,36 @@ class EppDomainUpdateBaseTest < EppTestCase
|
||||||
assert_epp_response :completed_successfully
|
assert_epp_response :completed_successfully
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_allow_underage_admin_contact
|
||||||
|
admin_contact = contacts(:william)
|
||||||
|
admin_contact.update!(
|
||||||
|
ident_type: 'priv',
|
||||||
|
ident: '61203150222',
|
||||||
|
ident_country_code: 'EE'
|
||||||
|
)
|
||||||
|
|
||||||
|
request_xml = <<-XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
|
||||||
|
<command>
|
||||||
|
<update>
|
||||||
|
<domain:update xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
|
||||||
|
<domain:name>#{@domain.name}</domain:name>
|
||||||
|
<domain:add>
|
||||||
|
<domain:contact type="admin">#{admin_contact.code}</domain:contact>
|
||||||
|
</domain:add>
|
||||||
|
</domain:update>
|
||||||
|
</update>
|
||||||
|
</command>
|
||||||
|
</epp>
|
||||||
|
XML
|
||||||
|
|
||||||
|
post epp_update_path, params: { frame: request_xml },
|
||||||
|
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
|
||||||
|
|
||||||
|
assert_epp_response :object_status_prohibits_operation
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def assert_verification_and_notification_emails
|
def assert_verification_and_notification_emails
|
||||||
|
|
|
@ -14,7 +14,7 @@ class DomainVersionTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assigns_creator_to_paper_trail_whodunnit
|
def test_assigns_creator_to_paper_trail_whodunnit
|
||||||
Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true }
|
Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true }.to_json
|
||||||
duplicate_domain = prepare_duplicate_domain
|
duplicate_domain = prepare_duplicate_domain
|
||||||
|
|
||||||
PaperTrail.request.whodunnit = @user.id_role_username
|
PaperTrail.request.whodunnit = @user.id_role_username
|
||||||
|
|
|
@ -14,4 +14,70 @@ class DomainContactTest < ActiveSupport::TestCase
|
||||||
assert @domain_contact.value_typeahead, 'Jane'
|
assert @domain_contact.value_typeahead, 'Jane'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_validates_admin_contact_age_with_birthday
|
||||||
|
admin_contact = contacts(:john)
|
||||||
|
admin_contact.update!(
|
||||||
|
ident_type: 'birthday',
|
||||||
|
ident: (Time.zone.now - 16.years).strftime('%Y-%m-%d')
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_contact = AdminDomainContact.new(
|
||||||
|
domain: domains(:shop),
|
||||||
|
contact: admin_contact
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_not domain_contact.valid?
|
||||||
|
assert_includes domain_contact.errors.full_messages,
|
||||||
|
'Contact Administrative contact must be at least 18 years old'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_validates_admin_contact_age_with_estonian_id
|
||||||
|
admin_contact = contacts(:john)
|
||||||
|
admin_contact.update!(
|
||||||
|
ident_type: 'priv',
|
||||||
|
ident: '61203150222',
|
||||||
|
ident_country_code: 'EE'
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_contact = AdminDomainContact.new(
|
||||||
|
domain: domains(:shop),
|
||||||
|
contact: admin_contact
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_not domain_contact.valid?
|
||||||
|
assert_includes domain_contact.errors.full_messages,
|
||||||
|
'Contact Administrative contact must be at least 18 years old'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_allows_adult_admin_contact_with_birthday
|
||||||
|
admin_contact = contacts(:john)
|
||||||
|
admin_contact.update!(
|
||||||
|
ident_type: 'birthday',
|
||||||
|
ident: (Time.zone.now - 20.years).strftime('%Y-%m-%d')
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_contact = AdminDomainContact.new(
|
||||||
|
domain: domains(:shop),
|
||||||
|
contact: admin_contact
|
||||||
|
)
|
||||||
|
|
||||||
|
assert domain_contact.valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_allows_adult_admin_contact_with_estonian_id
|
||||||
|
admin_contact = contacts(:john)
|
||||||
|
admin_contact.update!(
|
||||||
|
ident_type: 'priv',
|
||||||
|
ident: '38903111310',
|
||||||
|
ident_country_code: 'EE'
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_contact = AdminDomainContact.new(
|
||||||
|
domain: domains(:shop),
|
||||||
|
contact: admin_contact
|
||||||
|
)
|
||||||
|
|
||||||
|
assert domain_contact.valid?
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
|
@ -587,7 +587,14 @@ class DomainTest < ActiveSupport::TestCase
|
||||||
assert domain.invalid?
|
assert domain.invalid?
|
||||||
assert_includes domain.errors.full_messages, 'Admin domain contacts Admin contacts count must be between 1-10'
|
assert_includes domain.errors.full_messages, 'Admin domain contacts Admin contacts count must be between 1-10'
|
||||||
|
|
||||||
domain.admin_domain_contacts.build(contact: contacts(:john))
|
admin_contact = contacts(:john)
|
||||||
|
admin_contact.update!(
|
||||||
|
ident_type: 'priv',
|
||||||
|
ident: '37810166020',
|
||||||
|
ident_country_code: 'EE'
|
||||||
|
)
|
||||||
|
|
||||||
|
domain.admin_domain_contacts.build(contact: admin_contact)
|
||||||
assert domain.valid?
|
assert domain.valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue