Skip to content

Commit c4bbad9

Browse files
authored
Merge pull request #2748 from internetee/admin-contacts-in-settings-and-validation
feat: add admin contact ident type validation
2 parents 45703d4 + 2d103bd commit c4bbad9

File tree

15 files changed

+456
-17
lines changed

15 files changed

+456
-17
lines changed

app/controllers/admin/settings_controller.rb

+16-3
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,25 @@ def create
2727

2828
def casted_settings
2929
settings = {}
30-
30+
3131
params[:settings].each do |k, v|
32-
settings[k] = { value: v }
32+
setting = SettingEntry.find(k)
33+
value = if setting.format == 'array'
34+
processed_hash = available_options.each_with_object({}) do |option, hash|
35+
hash[option] = (v[option] == "true")
36+
end
37+
processed_hash.to_json
38+
else
39+
v
40+
end
41+
settings[k] = { value: value }
3342
end
34-
43+
3544
settings
3645
end
46+
47+
def available_options
48+
%w[birthday priv org]
49+
end
3750
end
3851
end

app/models/domain.rb

+18-2
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ def self.tech_contacts_validation_rules(for_org:)
186186
object_count: admin_contacts_validation_rules(for_org: false),
187187
unless: :require_admin_contacts?
188188

189+
validate :validate_admin_contacts_ident_type, on: :create
190+
189191
validates :tech_domain_contacts,
190192
object_count: tech_contacts_validation_rules(for_org: true),
191193
if: :require_tech_contacts?
@@ -857,10 +859,10 @@ def self.swap_elements(array, indexes)
857859
end
858860

859861
def require_admin_contacts?
860-
return true if registrant.org?
862+
return true if registrant.org? && Setting.admin_contacts_required_for_org
861863
return false unless registrant.priv?
862864

863-
underage_registrant?
865+
underage_registrant? && Setting.admin_contacts_required_for_minors
864866
end
865867

866868
def require_tech_contacts?
@@ -916,4 +918,18 @@ def parse_estonian_id_birth_date(id_code)
916918

917919
Date.parse("#{birth_year}-#{month}-#{day}")
918920
end
921+
922+
def validate_admin_contacts_ident_type
923+
allowed_types = Setting.admin_contacts_allowed_ident_type
924+
return if allowed_types.blank?
925+
926+
admin_contacts.each do |contact|
927+
next if allowed_types[contact.ident_type] == true
928+
929+
errors.add(:admin_contacts, I18n.t(
930+
'activerecord.errors.models.domain.admin_contact_invalid_ident_type',
931+
ident_type: contact.ident_type
932+
))
933+
end
934+
end
919935
end

app/models/epp/domain.rb

+27-4
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,35 @@ def validate_contacts
2929
active_admins = admin_domain_contacts.select { |x| !x.marked_for_destruction? }
3030
active_techs = tech_domain_contacts.select { |x| !x.marked_for_destruction? }
3131

32+
if require_admin_contacts? && active_admins.empty?
33+
add_epp_error('2306', 'contact', nil, 'Admin contact is required')
34+
ok = false
35+
end
36+
3237
# validate registrant here as well
3338
([Contact.find(registrant.id)] + active_admins + active_techs).each do |x|
3439
unless x.valid?
3540
add_epp_error('2304', nil, nil, I18n.t(:contact_is_not_valid, value: x.try(:code)))
3641
ok = false
3742
end
3843
end
44+
45+
# Validate admin contacts ident type only for new domains or new admin contacts
46+
allowed_types = Setting.admin_contacts_allowed_ident_type
47+
if allowed_types.present?
48+
active_admins.each do |admin_contact|
49+
next if !new_record? && admin_contact.persisted? && !admin_contact.changed?
50+
51+
contact = admin_contact.contact
52+
unless allowed_types[contact.ident_type] == true
53+
add_epp_error('2306', 'contact', contact.code,
54+
I18n.t('activerecord.errors.models.domain.admin_contact_invalid_ident_type',
55+
ident_type: contact.ident_type))
56+
ok = false
57+
end
58+
end
59+
end
60+
3961
ok
4062
end
4163

@@ -95,7 +117,8 @@ def epp_code_map
95117
[:base, :key_data_not_allowed],
96118
[:period, :not_a_number],
97119
[:period, :not_an_integer],
98-
[:registrant, :cannot_be_missing]
120+
[:registrant, :cannot_be_missing],
121+
[:admin_contacts, :invalid_ident_type]
99122
],
100123
'2308' => [
101124
[:base, :domain_name_blocked, { value: { obj: 'name', val: name_dirty } }],
@@ -414,15 +437,15 @@ def admin_contacts_validation_rules(for_org:)
414437
end
415438

416439
def require_admin_contacts?
417-
return true if registrant.org?
440+
return true if registrant.org? && Setting.admin_contacts_required_for_org
418441
return false unless registrant.priv?
419442

420-
underage_registrant?
443+
underage_registrant? && Setting.admin_contacts_required_for_minors
421444
end
422445

423446
def tech_contacts_validation_rules(for_org:)
424447
{
425-
min: 0, # Технический контакт опционален для всех
448+
min: 0,
426449
max: -> { Setting.tech_contacts_max_count }
427450
}
428451
end

app/models/setting_entry.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ def hash_format
8787
end
8888

8989
def array_format
90-
JSON.parse(value).to_a
90+
begin
91+
if value.is_a?(String)
92+
JSON.parse(value)
93+
elsif value.is_a?(Hash)
94+
value
95+
else
96+
{ 'birthday' => true, 'priv' => true, 'org' => true }
97+
end
98+
rescue JSON::ParserError => e
99+
puts "JSON Parse error: #{e.message}"
100+
{ 'birthday' => true, 'priv' => true, 'org' => true }
101+
end
91102
end
92103
end

app/views/admin/settings/_setting_row.haml

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
%tr{class: (@errors && @errors.has_key?(setting.code) && "danger")}
22
%td.col-md-6= setting.code.humanize
3-
- if [TrueClass, FalseClass].include?(setting.retrieve.class)
3+
- if setting.format == 'array'
4+
%td.col-md-6
5+
- available_options = ['birthday', 'priv', 'org']
6+
- begin
7+
- raw_value = setting.retrieve
8+
- current_values = if raw_value.is_a?(Hash)
9+
- raw_value
10+
- elsif raw_value.is_a?(Array) && raw_value.first.is_a?(Array)
11+
- Hash[raw_value]
12+
- elsif raw_value.is_a?(Array)
13+
- available_options.each_with_object({}) { |opt, hash| hash[opt] = raw_value.include?(opt) }
14+
- else
15+
- begin
16+
- parsed = JSON.parse(raw_value.to_s)
17+
- parsed.is_a?(Hash) ? parsed : available_options.each_with_object({}) { |opt, hash| hash[opt] = true }
18+
- rescue => e
19+
- available_options.each_with_object({}) { |opt, hash| hash[opt] = true }
20+
.row
21+
- available_options.each do |option|
22+
.col-md-4
23+
.checkbox
24+
%label
25+
= check_box_tag "settings[#{setting.id}][#{option}]", "true", current_values[option],
26+
id: "setting_#{setting.id}_#{option}",
27+
data: { value: current_values[option] }
28+
= option.humanize
29+
- elsif [TrueClass, FalseClass].include?(setting.retrieve.class)
430
%td.col-md-6
531
= hidden_field_tag("[settings][#{setting.id}]", '', id: nil)
632
= check_box_tag("[settings][#{setting.id}]", true, setting.retrieve)

config/locales/en.yml

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ en:
8181

8282
domain:
8383
<<: *epp_domain_ar_attributes
84+
admin_contact_invalid_ident_type: "Admin contact can be private person only"
8485

8586
nameserver:
8687
attributes:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class AddAdminContactsRulesToSettings < ActiveRecord::Migration[6.1]
2+
def up
3+
unless SettingEntry.exists?(code: 'admin_contacts_required_for_org')
4+
SettingEntry.create!(
5+
code: 'admin_contacts_required_for_org',
6+
value: 'true',
7+
format: 'boolean',
8+
group: 'domain_validation'
9+
)
10+
else
11+
puts "SettingEntry admin_contacts_required_for_org already exists"
12+
end
13+
14+
unless SettingEntry.exists?(code: 'admin_contacts_required_for_minors')
15+
SettingEntry.create!(
16+
code: 'admin_contacts_required_for_minors',
17+
value: 'true',
18+
format: 'boolean',
19+
group: 'domain_validation'
20+
)
21+
else
22+
puts "SettingEntry admin_contacts_required_for_minors already exists"
23+
end
24+
25+
unless SettingEntry.exists?(code: 'admin_contacts_allowed_ident_type')
26+
SettingEntry.create!(
27+
code: 'admin_contacts_allowed_ident_type',
28+
value: {
29+
'birthday' => true,
30+
'priv' => true,
31+
'org' => false
32+
}.to_json,
33+
format: 'array',
34+
group: 'domain_validation'
35+
)
36+
else
37+
puts "SettingEntry admin_contacts_allowed_ident_type already exists"
38+
end
39+
end
40+
41+
def down
42+
SettingEntry.where(code: [
43+
'admin_contacts_required_for_org',
44+
'admin_contacts_required_for_minors',
45+
'admin_contacts_allowed_ident_type'
46+
]).destroy_all
47+
end
48+
end

db/seeds.rb

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
SettingEntry.create(code: 'directo_sales_agent', value: 'HELEN', format: 'string', group: 'billing')
88
SettingEntry.create(code: 'admin_contacts_min_count', value: '1', format: 'integer', group: 'domain_validation')
99
SettingEntry.create(code: 'admin_contacts_max_count', value: '10', format: 'integer', group: 'domain_validation')
10+
SettingEntry.create(code: 'admin_contacts_required_for_org', value: 'true', format: 'boolean', group: 'domain_validation')
11+
SettingEntry.create(code: 'admin_contacts_required_for_minors', value: 'true', format: 'boolean', group: 'domain_validation')
12+
SettingEntry.create(
13+
code: 'admin_contacts_allowed_ident_type',
14+
value: {
15+
'birthday' => true,
16+
'priv' => true,
17+
'org' => true
18+
}.to_json,
19+
format: 'array',
20+
group: 'domain_validation'
21+
)
22+
1023
SettingEntry.create(code: 'tech_contacts_min_count', value: '1', format: 'integer', group: 'domain_validation')
1124
SettingEntry.create(code: 'tech_contacts_max_count', value: '10', format: 'integer', group: 'domain_validation')
1225
SettingEntry.create(code: 'orphans_contacts_in_months', value: '6', format: 'integer', group: 'domain_validation')

db/structure.sql

+53-2
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,38 @@ CREATE SEQUENCE public.epp_sessions_id_seq
11681168
ALTER SEQUENCE public.epp_sessions_id_seq OWNED BY public.epp_sessions.id;
11691169

11701170

1171+
--
1172+
-- Name: free_domain_reservation_holders; Type: TABLE; Schema: public; Owner: -
1173+
--
1174+
1175+
CREATE TABLE public.free_domain_reservation_holders (
1176+
id bigint NOT NULL,
1177+
user_unique_id character varying NOT NULL,
1178+
domain_names character varying[] DEFAULT '{}'::character varying[],
1179+
created_at timestamp(6) without time zone NOT NULL,
1180+
updated_at timestamp(6) without time zone NOT NULL
1181+
);
1182+
1183+
1184+
--
1185+
-- Name: free_domain_reservation_holders_id_seq; Type: SEQUENCE; Schema: public; Owner: -
1186+
--
1187+
1188+
CREATE SEQUENCE public.free_domain_reservation_holders_id_seq
1189+
START WITH 1
1190+
INCREMENT BY 1
1191+
NO MINVALUE
1192+
NO MAXVALUE
1193+
CACHE 1;
1194+
1195+
1196+
--
1197+
-- Name: free_domain_reservation_holders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
1198+
--
1199+
1200+
ALTER SEQUENCE public.free_domain_reservation_holders_id_seq OWNED BY public.free_domain_reservation_holders.id;
1201+
1202+
11711203
--
11721204
-- Name: invoice_items; Type: TABLE; Schema: public; Owner: -
11731205
--
@@ -2675,7 +2707,8 @@ CREATE TABLE public.reserved_domains (
26752707
updator_str character varying,
26762708
legacy_id integer,
26772709
name character varying NOT NULL,
2678-
password character varying NOT NULL
2710+
password character varying NOT NULL,
2711+
expire_at timestamp without time zone
26792712
);
26802713

26812714

@@ -3185,6 +3218,13 @@ ALTER TABLE ONLY public.epp_logs ALTER COLUMN id SET DEFAULT nextval('public.epp
31853218
ALTER TABLE ONLY public.epp_sessions ALTER COLUMN id SET DEFAULT nextval('public.epp_sessions_id_seq'::regclass);
31863219

31873220

3221+
--
3222+
-- Name: free_domain_reservation_holders id; Type: DEFAULT; Schema: public; Owner: -
3223+
--
3224+
3225+
ALTER TABLE ONLY public.free_domain_reservation_holders ALTER COLUMN id SET DEFAULT nextval('public.free_domain_reservation_holders_id_seq'::regclass);
3226+
3227+
31883228
--
31893229
-- Name: invoice_items id; Type: DEFAULT; Schema: public; Owner: -
31903230
--
@@ -3714,6 +3754,14 @@ ALTER TABLE ONLY public.epp_sessions
37143754
ADD CONSTRAINT epp_sessions_pkey PRIMARY KEY (id);
37153755

37163756

3757+
--
3758+
-- Name: free_domain_reservation_holders free_domain_reservation_holders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
3759+
--
3760+
3761+
ALTER TABLE ONLY public.free_domain_reservation_holders
3762+
ADD CONSTRAINT free_domain_reservation_holders_pkey PRIMARY KEY (id);
3763+
3764+
37173765
--
37183766
-- Name: invoice_items invoice_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
37193767
--
@@ -5667,6 +5715,9 @@ INSERT INTO "schema_migrations" (version) VALUES
56675715
('20241030095636'),
56685716
('20241104104620'),
56695717
('20241112093540'),
5670-
('20241112124405');
5718+
('20241112124405'),
5719+
('20241129095711'),
5720+
('20241206085817'),
5721+
('20250204094550');
56715722

56725723

test/fixtures/setting_entries.yml

+24
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,27 @@ ip_whitelist_max_count:
469469
format: integer
470470
created_at: <%= Time.zone.parse('2010-07-05') %>
471471
updated_at: <%= Time.zone.parse('2010-07-05') %>
472+
473+
admin_contacts_required_for_org:
474+
code: admin_contacts_required_for_org
475+
value: 'true'
476+
group: domain_validation
477+
format: boolean
478+
created_at: <%= Time.zone.parse('2010-07-05') %>
479+
updated_at: <%= Time.zone.parse('2010-07-05') %>
480+
481+
admin_contacts_required_for_minors:
482+
code: admin_contacts_required_for_minors
483+
value: 'true'
484+
group: domain_validation
485+
format: boolean
486+
created_at: <%= Time.zone.parse('2010-07-05') %>
487+
updated_at: <%= Time.zone.parse('2010-07-05') %>
488+
489+
admin_contacts_allowed_ident_type:
490+
code: admin_contacts_allowed_ident_type
491+
value: '{"birthday":true,"priv":true,"org":false}'
492+
group: domain_validation
493+
format: array
494+
created_at: <%= Time.zone.parse('2010-07-05') %>
495+
updated_at: <%= Time.zone.parse('2010-07-05') %>

0 commit comments

Comments
 (0)