diff --git a/app/models/registrar.rb b/app/models/registrar.rb index 040c7886b..149bb7541 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -137,7 +137,8 @@ class Registrar < ApplicationRecord def api_ip_white?(ip) return true unless Setting.api_ip_whitelist_enabled - white_ips.api.pluck(:ipv4, :ipv6).flatten.include?(ip) + + white_ips.api.include_ip?(ip) end # Audit log is needed, therefore no raw SQL diff --git a/app/models/white_ip.rb b/app/models/white_ip.rb index 303ff5886..417633b12 100644 --- a/app/models/white_ip.rb +++ b/app/models/white_ip.rb @@ -2,8 +2,8 @@ class WhiteIp < ApplicationRecord include Versions belongs_to :registrar - validates :ipv4, format: { with: /\A(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\z/, allow_blank: true } - validates :ipv6, format: { with: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/, allow_blank: true } + validate :valid_ipv4? + validate :valid_ipv6? validate :validate_ipv4_and_ipv6 def validate_ipv4_and_ipv6 @@ -11,6 +11,22 @@ class WhiteIp < ApplicationRecord errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present)) end + def valid_ipv4? + return if ipv4.blank? + + IPAddr.new(ipv4, Socket::AF_INET) + rescue StandardError => _e + errors.add(:ipv4, :invalid) + end + + def valid_ipv6? + return if ipv6.blank? + + IPAddr.new(ipv6, Socket::AF_INET6) + rescue StandardError => _e + errors.add(:ipv6, :invalid) + end + API = 'api' REGISTRAR = 'registrar' INTERFACES = [API, REGISTRAR] @@ -23,8 +39,37 @@ class WhiteIp < ApplicationRecord end class << self + # rubocop:disable Style/CaseEquality + # rubocop:disable Metrics/AbcSize def include_ip?(ip) - where('ipv4 = :ip OR ipv6 = :ip', ip: ip).any? + return false if ip.blank? + + where(id: ids_including(ip)).any? + end + + def ids_including(ip) + ipv4 = ipv6 = [] + if check_ip4(ip).present? + ipv4 = select { |white_ip| IPAddr.new(white_ip.ipv4, Socket::AF_INET) === check_ip4(ip) } + end + if check_ip6(ip).present? + ipv6 = select { |white_ip| IPAddr.new(white_ip.ipv6, Socket::AF_INET6) === check_ip6(ip) } + end + (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 end end diff --git a/test/system/registrar_area/base_test.rb b/test/system/registrar_area/base_test.rb index 67b19a044..2529b009a 100644 --- a/test/system/registrar_area/base_test.rb +++ b/test/system/registrar_area/base_test.rb @@ -29,6 +29,16 @@ class RegistrarAreaBaseTestTest < ApplicationSystemTestCase assert_button 'Login' end + def test_user_can_access_when_ip_is_whitelisted_with_subnet + white_ips(:one).update!(ipv4: '127.0.0.1/32', interfaces: [WhiteIp::REGISTRAR]) + Setting.registrar_ip_whitelist_enabled = true + + visit new_registrar_user_session_url + + assert_no_text 'Access denied from IP 127.0.0.1' + assert_button 'Login' + end + def test_user_can_access_when_ip_is_not_whitelisted_and_whitelist_is_disabled Setting.registrar_ip_whitelist_enabled = false WhiteIp.delete_all