mirror of
https://github.com/internetee/registry.git
synced 2025-07-28 13:36:15 +02:00
feat: add age validation for admin contacts
- Add AgeValidation module for consistent age checks - Validate admin contacts must be at least 18 years old - Move age validation logic from Domain to shared module - Add tests for admin contact age validation - Fix JSON format for admin_contacts_allowed_ident_type setting This change ensures that administrative contacts must be adults (18+), using the same age validation logic as for registrants. The validation works with both birthday and Estonian ID formats. Settings are now properly stored as JSON strings for consistent parsing.
This commit is contained in:
parent
66619f12fe
commit
7799727867
10 changed files with 181 additions and 51 deletions
|
@ -1,4 +1,8 @@
|
|||
class AdminDomainContact < DomainContact
|
||||
include AgeValidation
|
||||
|
||||
validate :validate_contact_age
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.replace(current_contact, new_contact)
|
||||
|
@ -23,4 +27,14 @@ class AdminDomainContact < DomainContact
|
|||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# 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
|
||||
|
|
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::CompanyRegister
|
||||
include EmailVerifable
|
||||
include AgeValidation
|
||||
|
||||
belongs_to :original, class_name: 'Contact'
|
||||
belongs_to :registrar, required: true
|
||||
|
|
|
@ -12,6 +12,7 @@ class Domain < ApplicationRecord
|
|||
include Domain::Releasable
|
||||
include Domain::Disputable
|
||||
include Domain::BulkUpdatable
|
||||
include AgeValidation
|
||||
|
||||
PERIODS = [
|
||||
['3 months', '3m'],
|
||||
|
@ -866,7 +867,7 @@ class Domain < ApplicationRecord
|
|||
return true if registrant.org? && Setting.admin_contacts_required_for_org
|
||||
return false unless registrant.priv?
|
||||
|
||||
underage_registrant? && Setting.admin_contacts_required_for_minors
|
||||
registrant.underage? && Setting.admin_contacts_required_for_minors
|
||||
end
|
||||
|
||||
def require_tech_contacts?
|
||||
|
@ -876,51 +877,7 @@ class Domain < ApplicationRecord
|
|||
private
|
||||
|
||||
def underage_registrant?
|
||||
case registrant.ident_type
|
||||
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}")
|
||||
registrant.underage?
|
||||
end
|
||||
|
||||
def validate_admin_contacts_ident_type
|
||||
|
|
|
@ -160,6 +160,8 @@ en:
|
|||
ipv6:
|
||||
taken: 'has already been taken'
|
||||
|
||||
admin_domain_contact:
|
||||
contact_too_young: "Administrative contact must be at least 18 years old"
|
||||
|
||||
attributes:
|
||||
epp_domain: &epp_domain_attributes
|
||||
|
|
|
@ -597,7 +597,7 @@ class EppDomainCreateBaseTest < EppTestCase
|
|||
end
|
||||
|
||||
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')
|
||||
travel_to now
|
||||
|
@ -647,7 +647,7 @@ 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 }
|
||||
Setting.admin_contacts_allowed_ident_type = { 'org' => false, 'priv' => true, 'birthday' => true }.to_json
|
||||
end
|
||||
|
||||
def test_registers_domain_without_legaldoc_if_optout
|
||||
|
|
|
@ -1032,7 +1032,7 @@ class EppDomainUpdateBaseTest < EppTestCase
|
|||
@domain.save!
|
||||
|
||||
# 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
|
||||
request_xml = <<-XML
|
||||
|
@ -1061,6 +1061,36 @@ class EppDomainUpdateBaseTest < EppTestCase
|
|||
assert_epp_response :completed_successfully
|
||||
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
|
||||
|
||||
def assert_verification_and_notification_emails
|
||||
|
|
|
@ -14,7 +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 }
|
||||
Setting.admin_contacts_allowed_ident_type = { 'org' => true, 'priv' => true, 'birthday' => true }.to_json
|
||||
duplicate_domain = prepare_duplicate_domain
|
||||
|
||||
PaperTrail.request.whodunnit = @user.id_role_username
|
||||
|
|
|
@ -14,4 +14,70 @@ class DomainContactTest < ActiveSupport::TestCase
|
|||
assert @domain_contact.value_typeahead, 'Jane'
|
||||
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
|
|
@ -587,7 +587,14 @@ class DomainTest < ActiveSupport::TestCase
|
|||
assert domain.invalid?
|
||||
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?
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue