diff --git a/Gemfile b/Gemfile index 68272c056..69cecca3a 100644 --- a/Gemfile +++ b/Gemfile @@ -89,6 +89,7 @@ group :test do gem 'capybara' gem 'database_cleaner' gem 'minitest', '~> 5.17' + gem 'minitest-stub_any_instance' gem 'simplecov', '0.17.1', require: false # CC last supported v0.17 gem 'spy' gem 'webdrivers' diff --git a/Gemfile.lock b/Gemfile.lock index 7e1dd7d1d..db74c3f63 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -311,6 +311,7 @@ GEM mini_mime (1.1.2) mini_portile2 (2.8.2) minitest (5.18.1) + minitest-stub_any_instance (1.0.3) monetize (1.9.4) money (~> 6.12) money (6.13.8) @@ -571,6 +572,7 @@ DEPENDENCIES mime-types-data mimemagic (= 0.4.3) minitest (~> 5.17) + minitest-stub_any_instance money-rails newrelic-infinite_tracing newrelic_rpm @@ -606,4 +608,4 @@ DEPENDENCIES wkhtmltopdf-binary (~> 0.12.6.1) BUNDLED WITH - 2.4.13 + 2.4.14 diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 88afec72d..36de86a2c 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -1,6 +1,7 @@ module Admin class BaseController < ApplicationController before_action :authenticate_admin_user! + before_action :set_locale helper_method :head_title_sufix before_action :set_paper_trail_whodunnit @@ -33,5 +34,9 @@ module Admin end end end + + def set_locale + I18n.locale = params[:locale] || I18n.default_locale + end end end diff --git a/app/controllers/admin/certificates_controller.rb b/app/controllers/admin/certificates_controller.rb index 3f1113b0f..56e30f26a 100644 --- a/app/controllers/admin/certificates_controller.rb +++ b/app/controllers/admin/certificates_controller.rb @@ -41,7 +41,7 @@ module Admin def sign if @certificate.sign!(password: certificate_params[:password]) flash[:notice] = I18n.t('record_updated') - notify_api_user + notify_registrar redirect_to [:admin, @api_user, @certificate] else flash.now[:alert] = I18n.t('failed_to_update_record') @@ -88,10 +88,10 @@ module Admin end end - def notify_api_user - api_user_email = @api_user.registrar.email + def notify_registrar + email = @api_user.registrar.email - CertificateMailer.signed(email: api_user_email, api_user: @api_user, + CertificateMailer.signed(email: email, api_user: @api_user, crt: OpenSSL::X509::Certificate.new(@certificate.crt)) .deliver_now end diff --git a/app/controllers/admin/white_ips_controller.rb b/app/controllers/admin/white_ips_controller.rb index 8554774d3..13867606e 100644 --- a/app/controllers/admin/white_ips_controller.rb +++ b/app/controllers/admin/white_ips_controller.rb @@ -2,17 +2,15 @@ module Admin class WhiteIpsController < BaseController load_and_authorize_resource - before_action :set_registrar, only: [:new, :show, :edit, :destroy, :update] + before_action :set_registrar, only: %i[new show edit destroy update] def new @white_ip = WhiteIp.new(registrar: @registrar) end - def show; - end + def show; end - def edit; - end + def edit; end def destroy if @white_ip.destroy @@ -38,7 +36,11 @@ module Admin end def update + previously_committed = @white_ip.committed + if @white_ip.update(white_ip_params) + notify_registrar if !previously_committed && @white_ip.committed + flash[:notice] = I18n.t('record_updated') redirect_to [:admin, @registrar, @white_ip] else @@ -54,7 +56,14 @@ module Admin end def white_ip_params - params.require(:white_ip).permit(:ipv4, :ipv6, :registrar_id, { interfaces: [] }) + params.require(:white_ip).permit(:ipv4, :ipv6, :registrar_id, :committed, { interfaces: [] }) + end + + def notify_registrar + email = @white_ip.registrar.email + + WhiteIpMailer.committed(email: email, ip: @white_ip) + .deliver_now end end end diff --git a/app/controllers/repp/v1/accounts_controller.rb b/app/controllers/repp/v1/accounts_controller.rb index 8aee2d294..2fe716e9c 100644 --- a/app/controllers/repp/v1/accounts_controller.rb +++ b/app/controllers/repp/v1/accounts_controller.rb @@ -174,7 +174,7 @@ module Repp end def serialized_ips(ips) - ips.as_json(only: %i[id ipv4 ipv6 interfaces]) + ips.as_json(only: %i[id ipv4 ipv6 interfaces committed]) end end end diff --git a/app/controllers/repp/v1/base_controller.rb b/app/controllers/repp/v1/base_controller.rb index 464be249a..d2057599e 100644 --- a/app/controllers/repp/v1/base_controller.rb +++ b/app/controllers/repp/v1/base_controller.rb @@ -5,6 +5,7 @@ module Repp around_action :log_request before_action :authenticate_user + before_action :set_locale before_action :validate_webclient_ca before_action :validate_client_certs before_action :check_ip_restriction @@ -121,18 +122,36 @@ module Repp end def check_ip_restriction - return if webclient_request? - return if @current_user.registrar.api_ip_white?(request.ip) + ip = webclient_request? ? request.headers['X-Client-IP'] : request.ip + return if registrar_ip_white?(ip) && webclient_request? + return if api_ip_white?(ip) && !webclient_request? - @response = { code: 2202, - message: I18n.t('registrar.authorization.ip_not_allowed', ip: request.ip) } - render(json: @response, status: :unauthorized) + render_unauthorized_response(ip) + end + + def registrar_ip_white?(ip) + return true unless ip + + @current_user.registrar.registrar_ip_white?(ip) + end + + def api_ip_white?(ip) + @current_user.registrar.api_ip_white?(ip) + end + + def render_unauthorized_response(ip) + @response = { code: 2202, message: I18n.t('registrar.authorization.ip_not_allowed', ip: ip) } + render json: @response, status: :unauthorized end def webclient_request? return false if Rails.env.test? || Rails.env.development? - ENV['webclient_ips'].split(',').map(&:strip).include?(request.ip) + webclient_ips.include?(request.ip) + end + + def webclient_ips + ENV['webclient_ips'].to_s.split(',').map(&:strip) end def validate_webclient_ca @@ -143,8 +162,7 @@ module Repp webclient_cn = ENV['webclient_cert_common_name'] || 'webclient' return if request_name == webclient_cn - @response = { code: 2202, - message: I18n.t('registrar.authorization.ip_not_allowed', ip: request.ip) } + @response = { code: 2202, message: 'Invalid webclient certificate' } render(json: @response, status: :unauthorized) end @@ -176,6 +194,10 @@ module Repp authorize!(:throttled_user, @domain) unless current_user || action_name == 'tara_callback' current_user end + + def set_locale + I18n.locale = current_user&.try(:locale) || params[:locale] || I18n.default_locale + end end end end diff --git a/app/controllers/repp/v1/white_ips_controller.rb b/app/controllers/repp/v1/white_ips_controller.rb index 4cb8d8987..c7ab1dc84 100644 --- a/app/controllers/repp/v1/white_ips_controller.rb +++ b/app/controllers/repp/v1/white_ips_controller.rb @@ -31,39 +31,63 @@ module Repp return end + uncommit_and_notify_admins if api_interface?(@white_ip) render_success(data: { ip: { id: @white_ip.id } }) end api :PUT, '/repp/v1/white_ips/:id' desc 'Update whitelisted IP address' def update + previously_api = api_interface?(@white_ip) unless @white_ip.update(white_ip_params) handle_non_epp_errors(@white_ip) return end + uncommit_and_notify_admins if api_interface?(@white_ip) + uncommit_and_notify_admins if previously_api && !api_interface?(@white_ip) render_success(data: { ip: { id: @white_ip.id } }) end api :DELETE, '/repp/v1/white_ips/:id' desc 'Delete a specific whitelisted IP address' def destroy + ip = @white_ip unless @white_ip.destroy handle_non_epp_errors(@white_ip) return end + uncommit_and_notify_admins(ip: ip, action: 'deleted') if api_interface?(ip) render_success end private + def api_interface?(ip) + ip.interfaces.include? WhiteIp::API + end + def find_white_ip @white_ip = current_user.registrar.white_ips.find(params[:id]) end def white_ip_params - params.require(:white_ip).permit(:ipv4, :ipv6, interfaces: []) + params.require(:white_ip).permit(:address, interfaces: []) + end + + def uncommit_and_notify_admins(ip: @white_ip, action: 'updated') + @white_ip.update(committed: false) if action == 'updated' + admin_users_emails = User.admin.pluck(:email).reject(&:blank?) + + return if admin_users_emails.empty? + + admin_users_emails.each do |email| + WhiteIpMailer.with(email: email, api_user: current_user, + white_ip: ip) + .send("api_ip_address_#{action}") + .deliver_now + end end end end diff --git a/app/mailers/white_ip_mailer.rb b/app/mailers/white_ip_mailer.rb new file mode 100644 index 000000000..8b7984ba8 --- /dev/null +++ b/app/mailers/white_ip_mailer.rb @@ -0,0 +1,23 @@ +class WhiteIpMailer < ApplicationMailer + def api_ip_address_updated + email = params[:email] + @api_user = params[:api_user] + @white_ip = params[:white_ip] + subject = '[Important] Whitelisted IP Address Change Notification' + mail(to: email, subject: subject) + end + + def api_ip_address_deleted + email = params[:email] + @api_user = params[:api_user] + @white_ip = params[:white_ip] + subject = '[Important] Whitelisted IP Address Removal Notification' + mail(to: email, subject: subject) + end + + def committed(email:, ip:) + @white_ip = ip + subject = 'Whitelisted IP Address Activation Confirmation' + mail(to: email, subject: subject) + end +end diff --git a/app/models/concerns/white_ip/white_ip_concern.rb b/app/models/concerns/white_ip/white_ip_concern.rb new file mode 100644 index 000000000..f44af0825 --- /dev/null +++ b/app/models/concerns/white_ip/white_ip_concern.rb @@ -0,0 +1,53 @@ +# app/models/concerns/white_ip_concern.rb +module WhiteIp::WhiteIpConcern + extend ActiveSupport::Concern + + class_methods do # rubocop:disable Metrics/BlockLength + def include_ip?(ip) + return false if ip.blank? + + where(id: ids_including(ip)).any? + end + + def ids_including(ip) + ipv4 = select_ipv4(ip) + ipv6 = select_ipv6(ip) + + (ipv4 + ipv6).pluck(:id).flatten.uniq + end + + # rubocop:disable Style/CaseEquality + def select_ipv4(ip) + return [] if check_ip4(ip).blank? + + select { |white_ip| check_ip4(white_ip.ipv4) === check_ip4(ip) } + end + + def select_ipv6(ip) + return [] if check_ip6(ip).blank? + + select { |white_ip| check_ip6(white_ip.ipv6) === check_ip6(ip) } + end + # rubocop:enable Style/CaseEquality + + def csv_header + %w[IPv4 IPv6 Interfaces Created Updated] + end + + def ransackable_attributes(*) + authorizable_ransackable_attributes + end + + def check_ip4(ip) + IPAddr.new(ip, Socket::AF_INET) + rescue StandardError => _e + nil + end + + def check_ip6(ip) + IPAddr.new(ip, Socket::AF_INET6) + rescue StandardError => _e + nil + end + end +end diff --git a/app/models/registrar.rb b/app/models/registrar.rb index cdcdbcc7b..71dddaa7f 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -200,6 +200,12 @@ class Registrar < ApplicationRecord # rubocop:disable Metrics/ClassLength white_ips.api.include_ip?(ip) end + def registrar_ip_white?(ip) + return true unless Setting.registrar_ip_whitelist_enabled + + white_ips.registrar_area.include_ip?(ip) + end + def accredited? api_users.any? do |a| return true unless a.accreditation_date.nil? diff --git a/app/models/white_ip.rb b/app/models/white_ip.rb index fb638c631..02840263f 100644 --- a/app/models/white_ip.rb +++ b/app/models/white_ip.rb @@ -1,11 +1,15 @@ class WhiteIp < ApplicationRecord include Versions + include WhiteIp::WhiteIpConcern + belongs_to :registrar + attr_accessor :address + + validate :validate_address_format + validate :validate_only_one_ip validate :valid_ipv4? validate :valid_ipv6? - validate :validate_ipv4_and_ipv6 - validate :validate_only_one_ip validate :validate_max_ip_count before_save :normalize_blank_values @@ -13,16 +17,35 @@ class WhiteIp < ApplicationRecord %i[ipv4 ipv6].each { |c| self[c].present? || self[c] = nil } end - def validate_ipv4_and_ipv6 - return if ipv4.present? || ipv6.present? + def validate_address_format + return if address.blank? - errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present)) + ip_address = IPAddr.new(address) + ip_version = determine_ip_version(ip_address) + + assign_ip_attributes(ip_version) + rescue IPAddr::InvalidAddressError + errors.add(:base, :address_invalid) end def validate_only_one_ip - return unless ipv4.present? && ipv6.present? + if ipv4.present? && ipv6.present? + errors.add(:base, :ip_must_be_one) + elsif ipv4.blank? && ipv6.blank? + errors.add(:base, :ipv4_or_ipv6_must_be_present) + end + end - errors.add(:base, I18n.t(:ip_must_be_one)) + def validate_max_ip_count + return if errors.any? + + total_exist = calculate_total_network_addresses(registrar.white_ips) + total_current = calculate_total_network_addresses([self]) + total = total_exist + total_current + limit = Setting.ip_whitelist_max_count + return unless total >= limit + + errors.add(:base, :ip_limit_exceeded, total: total, limit: limit) end def valid_ipv4? @@ -41,34 +64,9 @@ class WhiteIp < ApplicationRecord errors.add(:ipv6, :invalid) end - def validate_max_ip_count - return if errors.any? - - ip_addresses = registrar.white_ips - total = ip_addresses.size + count_network_addresses(ipv4.presence || ipv6) - limit = Setting.ip_whitelist_max_count - return unless total >= limit - - errors.add(:base, I18n.t(:ip_limit_exceeded, total: total, limit: limit)) - end - - def count_network_addresses(ip) - address = IPAddr.new(ip) - - if address.ipv4? - subnet_mask = address.prefix - 2**(32 - subnet_mask) - 2 - elsif address.ipv6? - subnet_mask = address.prefix - 2**(128 - subnet_mask) - 2 - else - 0 - end - end - API = 'api'.freeze REGISTRAR = 'registrar'.freeze - INTERFACES = [API, REGISTRAR].freeze + INTERFACES = [REGISTRAR, API].freeze scope :api, -> { where('interfaces @> ?::varchar[]', "{#{API}}") } scope :registrar_area, -> { where('interfaces @> ?::varchar[]', "{#{REGISTRAR}}") } @@ -77,52 +75,52 @@ class WhiteIp < ApplicationRecord super(interfaces.reject(&:blank?)) end - class << self - # rubocop:disable Style/CaseEquality - # rubocop:disable Metrics/AbcSize - def include_ip?(ip) - return false if ip.blank? - - where(id: ids_including(ip)).any? - end - - def ids_including(ip) - ipv4 = ipv6 = [] - ipv4 = select { |white_ip| check_ip4(white_ip.ipv4) === check_ip4(ip) } if check_ip4(ip).present? - ipv6 = select { |white_ip| check_ip6(white_ip.ipv6) === check_ip6(ip) } if check_ip6(ip).present? - (ipv4 + ipv6).pluck(:id).flatten.uniq - end - # rubocop:enable Style/CaseEquality - # rubocop:enable Metrics/AbcSize - - def check_ip4(ip) - IPAddr.new(ip, Socket::AF_INET) - rescue StandardError => _e - nil - end - - def check_ip6(ip) - IPAddr.new(ip, Socket::AF_INET6) - rescue StandardError => _e - nil - end - - def csv_header - %w[IPv4 IPv6 Interfaces Created Updated] - end - - def ransackable_attributes(*) - authorizable_ransackable_attributes - end - end - def as_csv_row [ - ipv4, - ipv6, + ipv4, ipv6, interfaces.join(', ').upcase, created_at, - updated_at, + updated_at ] end + + private + + def determine_ip_version(ip_address) + return :ipv4 if ip_address.ipv4? + return :ipv6 if ip_address.ipv6? + + nil + end + + def assign_ip_attributes(ip_version) + case ip_version + when :ipv4 + self.ipv4 = address + self.ipv6 = nil + when :ipv6 + self.ipv6 = address + self.ipv4 = nil + else + errors.add(:base, :address_invalid) + end + end + + def count_network_addresses(ip) + address = IPAddr.new(ip) + + if address.ipv4? + subnet_mask = address.prefix + (2**(32 - subnet_mask) - 2).abs + elsif address.ipv6? + subnet_mask = address.prefix + (2**(128 - subnet_mask) - 2).abs + else + 0 + end + end + + def calculate_total_network_addresses(ips) + ips.sum { |ip| count_network_addresses(ip.ipv4.presence || ip.ipv6) } + end end diff --git a/app/views/admin/registrars/show/_white_ips.html.erb b/app/views/admin/registrars/show/_white_ips.html.erb index 5de3066c0..df59c8b63 100644 --- a/app/views/admin/registrars/show/_white_ips.html.erb +++ b/app/views/admin/registrars/show/_white_ips.html.erb @@ -9,6 +9,7 @@ <%= WhiteIp.human_attribute_name :ipv4 %> <%= WhiteIp.human_attribute_name :ipv6 %> <%= WhiteIp.human_attribute_name :interfaces %> + <%= WhiteIp.human_attribute_name :committed %> @@ -26,6 +27,7 @@ <% end %> <%= white_ip.interfaces.join(', ').upcase %> + <%= white_ip.committed %> <% end %> diff --git a/app/views/admin/white_ips/_form.haml b/app/views/admin/white_ips/_form.haml index 7a0371697..2236c09ad 100644 --- a/app/views/admin/white_ips/_form.haml +++ b/app/views/admin/white_ips/_form.haml @@ -26,7 +26,13 @@ .col-md-7 = f.check_box :interfaces, { multiple: true }, x, nil = hidden_field_tag "white_ip[interfaces][]", nil + .form-group + .col-md-4.control-label + = f.label :committed + .col-md-7 + = f.check_box :committed + %hr .row .col-md-8.text-right - = button_tag(t(:save), class: 'btn btn-primary') + = button_tag(t(:save), class: 'btn btn-primary') \ No newline at end of file diff --git a/app/views/admin/white_ips/show.haml b/app/views/admin/white_ips/show.haml index 2ec354aa4..d3df82e93 100644 --- a/app/views/admin/white_ips/show.haml +++ b/app/views/admin/white_ips/show.haml @@ -22,3 +22,6 @@ %dt= WhiteIp.human_attribute_name :interfaces %dd= @white_ip.interfaces.join(', ').upcase + + %dt= WhiteIp.human_attribute_name :committed + %dd= @white_ip.committed diff --git a/app/views/mailers/white_ip_mailer/api_ip_address_deleted.html.erb b/app/views/mailers/white_ip_mailer/api_ip_address_deleted.html.erb new file mode 100644 index 000000000..fe558d5e4 --- /dev/null +++ b/app/views/mailers/white_ip_mailer/api_ip_address_deleted.html.erb @@ -0,0 +1,10 @@ +

This email is to inform you that an API Whitelisted IP address was deleted by Registrar Portal API user. Please review the details below:

+ + + +

Please take the necessary actions to ensure the security and integrity of the application's API access.

diff --git a/app/views/mailers/white_ip_mailer/api_ip_address_deleted.text.erb b/app/views/mailers/white_ip_mailer/api_ip_address_deleted.text.erb new file mode 100644 index 000000000..73eca3594 --- /dev/null +++ b/app/views/mailers/white_ip_mailer/api_ip_address_deleted.text.erb @@ -0,0 +1,8 @@ +This email is to inform you about an API Whitelisted IP address change by Registrar Portal API user. Please review the details below: + +Registrar: <%= @api_user.registrar.name %> +API User: <%= @api_user.username %> +IP Address: <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> +Interface: <%= @white_ip.interfaces.join(', ') %> + +Please take the necessary actions to ensure the security and integrity of the application's API access. diff --git a/app/views/mailers/white_ip_mailer/api_ip_address_updated.html.erb b/app/views/mailers/white_ip_mailer/api_ip_address_updated.html.erb new file mode 100644 index 000000000..927adc50f --- /dev/null +++ b/app/views/mailers/white_ip_mailer/api_ip_address_updated.html.erb @@ -0,0 +1,10 @@ +

This email is to inform you about an API Whitelisted IP address change by Registrar Portal API user. Please review the details below:

+ + + +

Please take the necessary actions to ensure the security and integrity of the application's API access.

diff --git a/app/views/mailers/white_ip_mailer/api_ip_address_updated.text.erb b/app/views/mailers/white_ip_mailer/api_ip_address_updated.text.erb new file mode 100644 index 000000000..73eca3594 --- /dev/null +++ b/app/views/mailers/white_ip_mailer/api_ip_address_updated.text.erb @@ -0,0 +1,8 @@ +This email is to inform you about an API Whitelisted IP address change by Registrar Portal API user. Please review the details below: + +Registrar: <%= @api_user.registrar.name %> +API User: <%= @api_user.username %> +IP Address: <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> +Interface: <%= @white_ip.interfaces.join(', ') %> + +Please take the necessary actions to ensure the security and integrity of the application's API access. diff --git a/app/views/mailers/white_ip_mailer/committed.html.erb b/app/views/mailers/white_ip_mailer/committed.html.erb new file mode 100644 index 000000000..2cc853ea3 --- /dev/null +++ b/app/views/mailers/white_ip_mailer/committed.html.erb @@ -0,0 +1,43 @@ +Tere, +

+

+Anname teada, et Teie konto juurde kuuluv lubatud IP-aadress on edukalt aktiveeritud. +

+

IP-aadressi andmed:

+ +

+Päringud IP-aadressist <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> jõuavad nüüd .ee registrisüsteemini. +Palun hoidke selle IP-aadressi turvalisust ja konfidentsiaalsust, et vältida volitamata juurdepääsu. +

+Kui Teil on küsimusi seoses Teie lubatud IP-aadressi aktiveerimisega, võtke meiega ühendust. +

+

+Parimate soovidega, +

+<%= render 'mailers/shared/signatures/signature.et.html' %> +
+

+ +Hi, +

+

+We would like to inform you that the whitelisted IP address associated with your account has been successfully activated. +

+

IP address Details:

+ +

+All requests originating from the IP address <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> will now have access to our system. +Please ensure the security and confidentiality of this IP address to prevent unauthorized access. +

+If you have any questions regarding the activation of your whitelisted IP address, please contact us. +

+

+Best regards, +

+<%= render 'mailers/shared/signatures/signature.en.html' %> \ No newline at end of file diff --git a/app/views/mailers/white_ip_mailer/committed.text.erb b/app/views/mailers/white_ip_mailer/committed.text.erb new file mode 100644 index 000000000..8c8928485 --- /dev/null +++ b/app/views/mailers/white_ip_mailer/committed.text.erb @@ -0,0 +1,32 @@ +Tere, + +Anname teada, et Teie konto juurde kuuluv lubatud IP-aadress on edukalt aktiveeritud. + +IP-aadressi andmed: +- IP-aadress: <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> +- Liidesed: <%= @white_ip.interfaces.join(', ') %> + +Päringud IP-aadressist <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> jõuavad nüüd .ee registrisüsteemini. +Palun hoidke selle IP-aadressi turvalisust ja konfidentsiaalsust, et vältida volitamata juurdepääsu. + +Kui Teil on küsimusi seoses Teie lubatud IP-aadressi aktiveerimisega, võtke meiega ühendust. + +Parimate soovidega, +<%= render 'mailers/shared/signatures/signature.et.html' %> +--- + +Hi, + +We would like to inform you that the whitelisted IP address associated with your account has been successfully activated. + +IP address Details: +- IP address: <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> +- Interfaces: <%= @white_ip.interfaces.join(', ') %> + +All requests originating from the IP address <%= @white_ip.ipv4.presence || @white_ip.ipv6 %> will now have access to our system. +Please ensure the security and confidentiality of this IP address to prevent unauthorized access. + +If you have any questions regarding the activation of your whitelisted IP address, please contact us. + +Best regards, +<%= render 'mailers/shared/signatures/signature.en.html' %> \ No newline at end of file diff --git a/config/locales/devise.et.yml b/config/locales/devise.et.yml new file mode 100644 index 000000000..051ad2ea2 --- /dev/null +++ b/config/locales/devise.et.yml @@ -0,0 +1,64 @@ +# Additional translations at https://github.com/plataformatec/devise/wiki/I18n + +et: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + email_changed: + subject: "Email Changed" + password_change: + subject: "Password Changed" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + updated: "Your account has been updated successfully." + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/locales/en.yml b/config/locales/en.yml index 32f122924..e9553aa20 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -147,6 +147,18 @@ en: length_more_than: 'Parameter value policy error: Legaldoc size is less than minimum allowed size of 3kB' length_less_than: 'Parameter value policy error: Legaldoc size exceeds maximum allowed size of 8mB' + white_ip: + attributes: + base: + address_invalid: 'IP Address is invalid' + ipv4_or_ipv6_must_be_present: 'IPv4 or IPv6 must be present' + ip_must_be_one: 'Please enter only one IP address' + ip_limit_exceeded: 'IP address limit exceeded. Total addresses: %{total}. Limit: %{limit}.' + ipv4: + taken: 'has already been taken' + ipv6: + taken: 'has already been taken' + attributes: epp_domain: &epp_domain_attributes @@ -548,9 +560,6 @@ en: not_valid_domain_verification_body: This could mean your verification has been expired or done already.

Please contact us if you think something is wrong. upload_crt: 'Upload CRT' crt_or_csr_must_be_present: 'CRT or CSR must be present' - ipv4_or_ipv6_must_be_present: 'IPv4 or IPv6 must be present' - ip_must_be_one: 'Please enter only one IP address' - ip_limit_exceeded: 'IP address limit exceeded. Total addresses: %{total}. Limit: %{limit}.' white_ip: 'White IP' edit_white_ip: 'Edit white IP' confirm_domain_delete: 'Confirm domain delete' diff --git a/config/locales/et.yml b/config/locales/et.yml index b381259a7..651f5ff83 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -2,6 +2,14 @@ et: username: 'Kasutajanimi' password: 'Parool' + time: + formats: + default: "%Y-%m-%d %H:%M" + long: "%A, %e. %B %Y, %H:%M" + short: "%d.%m.%y, %H:%M" + date: "%Y-%m-%d" + date_long: "%d. %B %Y" + filename: "%Y-%m-%d_%H.%M" date: month_names: [~, Jaanuar, Veebruar, Märts, April, Mai, Juuni, Juuli, August, September, Oktoober, November, Detsember] @@ -17,3 +25,18 @@ et: you_have_a_new_invoice: 'Teil on uus arve.' monthly_invoice: "Siit tuleb aruanne möödunud kuul ettemaksukontoga seotud tasuliste toimingutega." sincerely: 'Lugupidamisega' + + activerecord: + errors: + models: + white_ip: + attributes: + base: + address_invalid: 'IP-aadress on vale' + ipv4_or_ipv6_must_be_present: 'Peab olema vähemalt üks IPv4 või IPv6' + ip_must_be_one: 'Palun sisestage ainult üks IP-aadress' + ip_limit_exceeded: 'IP-aadressi piirang ületatud. Kokku aadresse: %{total}. Limiit: %{limit}.' + ipv4: + taken: 'on juba lisatud' + ipv6: + taken: 'on juba lisatud' diff --git a/db/migrate/20230707084741_add_committed_to_white_ips.rb b/db/migrate/20230707084741_add_committed_to_white_ips.rb new file mode 100644 index 000000000..751bcc14c --- /dev/null +++ b/db/migrate/20230707084741_add_committed_to_white_ips.rb @@ -0,0 +1,5 @@ +class AddCommittedToWhiteIps < ActiveRecord::Migration[6.1] + def change + add_column :white_ips, :committed, :boolean, default: true + end +end diff --git a/db/structure.sql b/db/structure.sql index 44ffd7162..117880656 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2806,7 +2806,8 @@ CREATE TABLE public.white_ips ( created_at timestamp without time zone, updated_at timestamp without time zone, creator_str character varying, - updator_str character varying + updator_str character varying, + committed boolean DEFAULT true ); @@ -5468,6 +5469,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20221207102831'), ('20221214073933'), ('20221214074252'), -('20230531111154'); +('20230531111154'), +('20230707084741'); diff --git a/test/integration/admin_area/white_ips_test.rb b/test/integration/admin_area/white_ips_test.rb index 197759387..06f314e77 100644 --- a/test/integration/admin_area/white_ips_test.rb +++ b/test/integration/admin_area/white_ips_test.rb @@ -46,9 +46,21 @@ class AdminAreaWhiteIpsIntegrationTest < JavaScriptApplicationSystemTestCase fill_in 'IPv4', with: '127.0.0.2' find(:css, '#white_ip_interfaces_api').set(false) + find(:css, '#white_ip_committed').set(false) click_on 'Save' assert_text 'Record updated' + + click_on 'Edit' + + find(:css, '#white_ip_interfaces_api').set(false) + find(:css, '#white_ip_committed').set(true) + click_on 'Save' + + assert_text 'Record updated' + last_email = ActionMailer::Base.deliveries.last + assert last_email.subject.include?('Whitelisted IP Address Activation Confirmation') + assert last_email.to.include?(@registrar.email) end def test_failed_to_update_whitelisted_ip diff --git a/test/integration/repp/v1/base_test.rb b/test/integration/repp/v1/base_test.rb index d0baed30e..ed69eba13 100644 --- a/test/integration/repp/v1/base_test.rb +++ b/test/integration/repp/v1/base_test.rb @@ -51,13 +51,39 @@ class ReppV1BaseTest < ActionDispatch::IntegrationTest whiteip.update(ipv4: '1.1.1.1') get repp_v1_contacts_path, headers: @auth_headers + + assert_unauthorized_ip + + Setting.api_ip_whitelist_enabled = false + Setting.registrar_ip_whitelist_enabled = false + end + + def test_takes_ip_whitelist_into_account_if_webclient_request + Setting.api_ip_whitelist_enabled = true + Setting.registrar_ip_whitelist_enabled = true + + whiteip = white_ips(:one) + whiteip.update(interfaces: ['api']) + + Repp::V1::BaseController.stub_any_instance(:webclient_request?, true) do + Repp::V1::BaseController.stub_any_instance(:validate_webclient_ca, true) do + get repp_v1_contacts_path, headers: @auth_headers.merge!({ 'X-Client-IP' => whiteip.ipv4 }) + end + end + + assert_unauthorized_ip + + Setting.api_ip_whitelist_enabled = false + Setting.registrar_ip_whitelist_enabled = false + end + + private + + def assert_unauthorized_ip response_json = JSON.parse(response.body, symbolize_names: true) assert_response :unauthorized assert_equal 2202, response_json[:code] assert response_json[:message].include? 'Access denied from IP' - - Setting.api_ip_whitelist_enabled = false - Setting.registrar_ip_whitelist_enabled = false end end diff --git a/test/integration/repp/v1/registrar/auth/check_info_test.rb b/test/integration/repp/v1/registrar/auth/check_info_test.rb index 154e8e258..8fd321db1 100644 --- a/test/integration/repp/v1/registrar/auth/check_info_test.rb +++ b/test/integration/repp/v1/registrar/auth/check_info_test.rb @@ -8,7 +8,7 @@ class ReppV1RegistrarAuthCheckInfoTest < ActionDispatch::IntegrationTest @auth_headers = { 'Authorization' => token } - adapter = ENV["shunter_default_adapter"].constantize.new + adapter = ENV['shunter_default_adapter'].constantize.new adapter&.clear! end @@ -40,8 +40,8 @@ class ReppV1RegistrarAuthCheckInfoTest < ActionDispatch::IntegrationTest end def test_returns_error_response_if_throttled - ENV["shunter_default_threshold"] = '1' - ENV["shunter_enabled"] = 'true' + ENV['shunter_default_threshold'] = '1' + ENV['shunter_enabled'] = 'true' get '/repp/v1/registrar/auth', headers: @auth_headers get '/repp/v1/registrar/auth', headers: @auth_headers @@ -50,7 +50,7 @@ class ReppV1RegistrarAuthCheckInfoTest < ActionDispatch::IntegrationTest assert_response :bad_request assert_equal json[:code], 2502 assert response.body.include?(Shunter.default_error_message) - ENV["shunter_default_threshold"] = '10000' - ENV["shunter_enabled"] = 'false' + ENV['shunter_default_threshold'] = '10000' + ENV['shunter_enabled'] = 'false' end end diff --git a/test/integration/repp/v1/white_ips/create_test.rb b/test/integration/repp/v1/white_ips/create_test.rb index 2b152d41c..fcbe5e987 100644 --- a/test/integration/repp/v1/white_ips/create_test.rb +++ b/test/integration/repp/v1/white_ips/create_test.rb @@ -15,9 +15,8 @@ class ReppV1WhiteIpsCreateTest < ActionDispatch::IntegrationTest def test_creates_new_white_ip request_body = { white_ip: { - ipv4: '127.0.0.1', - ipv6: '', - interfaces: ['API'], + address: '127.1.1.1', + interfaces: ['api'], }, } @@ -31,15 +30,42 @@ class ReppV1WhiteIpsCreateTest < ActionDispatch::IntegrationTest white_ip = WhiteIp.find(json[:data][:ip][:id]) assert white_ip.present? - assert_equal(request_body[:white_ip][:ipv4], white_ip.ipv4) + assert_equal(request_body[:white_ip][:address], white_ip.ipv4) + refute white_ip.committed + + last_email = ActionMailer::Base.deliveries.last + assert last_email.subject.include?('Whitelisted IP Address Change Notification') + end + + def test_creates_new_white_ip_with_registrar_interface + request_body = { + white_ip: { + address: '127.1.1.1', + interfaces: ['registrar'], + }, + } + + post '/repp/v1/white_ips', headers: @auth_headers, params: request_body + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :ok + assert_equal 1000, json[:code] + assert_equal 'Command completed successfully', json[:message] + + white_ip = WhiteIp.find(json[:data][:ip][:id]) + assert white_ip.present? + + assert_equal(request_body[:white_ip][:address], white_ip.ipv4) + assert white_ip.committed + + refute ActionMailer::Base.deliveries.last end def test_validates_ip_max_count request_body = { white_ip: { - ipv4: '', - ipv6: '2001:db8::/120', - interfaces: ['API'], + address: '2001:db8::/120', + interfaces: ['api'], }, } @@ -56,9 +82,8 @@ class ReppV1WhiteIpsCreateTest < ActionDispatch::IntegrationTest request_body = { white_ip: { - ipv4: '127.0.0.1', - ipv6: '', - interfaces: ['API'], + address: '127.0.0.1', + interfaces: ['api'], }, } diff --git a/test/integration/repp/v1/white_ips/delete_test.rb b/test/integration/repp/v1/white_ips/delete_test.rb index e37d346e5..8300e2b4d 100644 --- a/test/integration/repp/v1/white_ips/delete_test.rb +++ b/test/integration/repp/v1/white_ips/delete_test.rb @@ -21,6 +21,9 @@ class ReppV1WhiteIpsDeleteTest < ActionDispatch::IntegrationTest assert_equal 1000, json[:code] assert_equal 'Command completed successfully', json[:message] refute WhiteIp.exists?(ip.id) + + last_email = ActionMailer::Base.deliveries.last + assert last_email.subject.include?('Whitelisted IP Address Removal Notification') end def test_returns_error_response_if_throttled diff --git a/test/integration/repp/v1/white_ips/update_test.rb b/test/integration/repp/v1/white_ips/update_test.rb index 03b34ef01..a38aba671 100644 --- a/test/integration/repp/v1/white_ips/update_test.rb +++ b/test/integration/repp/v1/white_ips/update_test.rb @@ -16,7 +16,7 @@ class ReppV1ApiWhiteIpsUpdateTest < ActionDispatch::IntegrationTest def test_updates_white_ip request_body = { white_ip: { - ipv4: '127.0.0.1', + address: '127.0.0.2', }, } @@ -28,13 +28,17 @@ class ReppV1ApiWhiteIpsUpdateTest < ActionDispatch::IntegrationTest assert_equal 'Command completed successfully', json[:message] ip = WhiteIp.find(json[:data][:ip][:id]) - assert_equal ip.ipv4, @white_ip.ipv4 + assert_equal ip.ipv4, '127.0.0.2' + refute ip.committed + + last_email = ActionMailer::Base.deliveries.last + assert last_email.subject.include?('Whitelisted IP Address Change Notification') end def test_returns_error_if_ipv4_wrong_format request_body = { white_ip: { - ipv4: 'wrongip', + address: 'wrongip', }, } @@ -42,21 +46,7 @@ class ReppV1ApiWhiteIpsUpdateTest < ActionDispatch::IntegrationTest json = JSON.parse(response.body, symbolize_names: true) assert_response :bad_request - assert json[:message].include? 'IPv4 is invalid' - end - - def test_returns_error_if_both_ips - request_body = { - white_ip: { - ipv6: '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - }, - } - - put "/repp/v1/white_ips/#{@white_ip.id}", headers: @auth_headers, params: request_body - json = JSON.parse(response.body, symbolize_names: true) - - assert_response :bad_request - assert json[:message].include? 'Please enter only one IP address' + assert json[:message].include? 'Address is invalid' end def test_returns_error_response_if_throttled @@ -65,7 +55,7 @@ class ReppV1ApiWhiteIpsUpdateTest < ActionDispatch::IntegrationTest request_body = { white_ip: { - ipv4: '127.0.0.1', + address: '127.0.0.1', }, } diff --git a/test/test_helper.rb b/test/test_helper.rb index 0cd407f84..ca63e2f35 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -19,6 +19,7 @@ require 'webmock/minitest' require 'support/assertions/epp_assertions' require 'sidekiq/testing' require 'spy/integration' +require 'minitest/stub_any_instance' Sidekiq::Testing.fake!