Skip to content

Commit

Permalink
Merge pull request #2748 from internetee/admin-contacts-in-settings-a…
Browse files Browse the repository at this point in the history
…nd-validation

feat: add admin contact ident type validation
  • Loading branch information
vohmar authored Feb 6, 2025
2 parents 45703d4 + 2d103bd commit c4bbad9
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 17 deletions.
19 changes: 16 additions & 3 deletions app/controllers/admin/settings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,25 @@ def create

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
20 changes: 18 additions & 2 deletions app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ def self.tech_contacts_validation_rules(for_org:)
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?
Expand Down Expand Up @@ -857,10 +859,10 @@ def self.swap_elements(array, indexes)
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?
Expand Down Expand Up @@ -916,4 +918,18 @@ def parse_estonian_id_birth_date(id_code)

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
31 changes: 27 additions & 4 deletions app/models/epp/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,35 @@ def validate_contacts
active_admins = admin_domain_contacts.select { |x| !x.marked_for_destruction? }
active_techs = tech_domain_contacts.select { |x| !x.marked_for_destruction? }

if require_admin_contacts? && active_admins.empty?
add_epp_error('2306', 'contact', nil, 'Admin contact is required')
ok = false
end

# validate registrant here as well
([Contact.find(registrant.id)] + active_admins + active_techs).each do |x|
unless x.valid?
add_epp_error('2304', nil, nil, I18n.t(:contact_is_not_valid, value: x.try(:code)))
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

Expand Down Expand Up @@ -95,7 +117,8 @@ def epp_code_map
[: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 } }],
Expand Down Expand Up @@ -414,15 +437,15 @@ def admin_contacts_validation_rules(for_org:)
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
Expand Down
13 changes: 12 additions & 1 deletion app/models/setting_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ def hash_format
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
28 changes: 27 additions & 1 deletion app/views/admin/settings/_setting_row.haml
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ en:

domain:
<<: *epp_domain_ar_attributes
admin_contact_invalid_ident_type: "Admin contact can be private person only"

nameserver:
attributes:
Expand Down
48 changes: 48 additions & 0 deletions db/migrate/20250204094550_add_admin_contacts_rules_to_settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class AddAdminContactsRulesToSettings < ActiveRecord::Migration[6.1]
def up
unless SettingEntry.exists?(code: 'admin_contacts_required_for_org')
SettingEntry.create!(
code: 'admin_contacts_required_for_org',
value: 'true',
format: 'boolean',
group: 'domain_validation'
)
else
puts "SettingEntry admin_contacts_required_for_org already exists"
end

unless SettingEntry.exists?(code: 'admin_contacts_required_for_minors')
SettingEntry.create!(
code: 'admin_contacts_required_for_minors',
value: 'true',
format: 'boolean',
group: 'domain_validation'
)
else
puts "SettingEntry admin_contacts_required_for_minors already exists"
end

unless SettingEntry.exists?(code: 'admin_contacts_allowed_ident_type')
SettingEntry.create!(
code: 'admin_contacts_allowed_ident_type',
value: {
'birthday' => true,
'priv' => true,
'org' => false
}.to_json,
format: 'array',
group: 'domain_validation'
)
else
puts "SettingEntry admin_contacts_allowed_ident_type already exists"
end
end

def down
SettingEntry.where(code: [
'admin_contacts_required_for_org',
'admin_contacts_required_for_minors',
'admin_contacts_allowed_ident_type'
]).destroy_all
end
end
13 changes: 13 additions & 0 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@
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')
Expand Down
55 changes: 53 additions & 2 deletions db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,38 @@ CREATE SEQUENCE public.epp_sessions_id_seq
ALTER SEQUENCE public.epp_sessions_id_seq OWNED BY public.epp_sessions.id;


--
-- Name: free_domain_reservation_holders; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.free_domain_reservation_holders (
id bigint NOT NULL,
user_unique_id character varying NOT NULL,
domain_names character varying[] DEFAULT '{}'::character varying[],
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);


--
-- Name: free_domain_reservation_holders_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.free_domain_reservation_holders_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


--
-- Name: free_domain_reservation_holders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.free_domain_reservation_holders_id_seq OWNED BY public.free_domain_reservation_holders.id;


--
-- Name: invoice_items; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -2675,7 +2707,8 @@ CREATE TABLE public.reserved_domains (
updator_str character varying,
legacy_id integer,
name character varying NOT NULL,
password character varying NOT NULL
password character varying NOT NULL,
expire_at timestamp without time zone
);


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


--
-- Name: free_domain_reservation_holders id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.free_domain_reservation_holders ALTER COLUMN id SET DEFAULT nextval('public.free_domain_reservation_holders_id_seq'::regclass);


--
-- Name: invoice_items id; Type: DEFAULT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -3714,6 +3754,14 @@ ALTER TABLE ONLY public.epp_sessions
ADD CONSTRAINT epp_sessions_pkey PRIMARY KEY (id);


--
-- Name: free_domain_reservation_holders free_domain_reservation_holders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.free_domain_reservation_holders
ADD CONSTRAINT free_domain_reservation_holders_pkey PRIMARY KEY (id);


--
-- Name: invoice_items invoice_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -5667,6 +5715,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20241030095636'),
('20241104104620'),
('20241112093540'),
('20241112124405');
('20241112124405'),
('20241129095711'),
('20241206085817'),
('20250204094550');


24 changes: 24 additions & 0 deletions test/fixtures/setting_entries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') %>
Loading

0 comments on commit c4bbad9

Please sign in to comment.