From bde0f1239ab016a6dfc8229ba579c5e2cf96a8e6 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 8 Nov 2024 05:08:36 -0500
Subject: [PATCH] Add `InetContainer` with scopes of `containing` and
 `contained` (#32802)

---
 app/helpers/registration_helper.rb     |  2 +-
 app/lib/suspicious_sign_in_detector.rb |  2 +-
 app/models/concerns/inet_container.rb  | 10 ++++++++++
 app/models/ip_block.rb                 |  1 +
 app/models/user.rb                     |  4 ++--
 app/models/user_ip.rb                  |  1 +
 lib/mastodon/cli/ip_blocks.rb          |  4 ++--
 7 files changed, 18 insertions(+), 6 deletions(-)
 create mode 100644 app/models/concerns/inet_container.rb

diff --git a/app/helpers/registration_helper.rb b/app/helpers/registration_helper.rb
index ef5462ac8..0f141c435 100644
--- a/app/helpers/registration_helper.rb
+++ b/app/helpers/registration_helper.rb
@@ -16,6 +16,6 @@ module RegistrationHelper
   end
 
   def ip_blocked?(remote_ip)
-    IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
+    IpBlock.where(severity: :sign_up_block).containing(remote_ip.to_s).exists?
   end
 end
diff --git a/app/lib/suspicious_sign_in_detector.rb b/app/lib/suspicious_sign_in_detector.rb
index 74f49aa55..60e5fdad4 100644
--- a/app/lib/suspicious_sign_in_detector.rb
+++ b/app/lib/suspicious_sign_in_detector.rb
@@ -19,7 +19,7 @@ class SuspiciousSignInDetector
   end
 
   def previously_seen_ip?(request)
-    @user.ips.exists?(['ip <<= ?', masked_ip(request)])
+    @user.ips.contained_by(masked_ip(request)).exists?
   end
 
   def freshly_signed_up?
diff --git a/app/models/concerns/inet_container.rb b/app/models/concerns/inet_container.rb
new file mode 100644
index 000000000..da03bcc5d
--- /dev/null
+++ b/app/models/concerns/inet_container.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module InetContainer
+  extend ActiveSupport::Concern
+
+  included do
+    scope :containing, ->(value) { where('ip >>= ?', value) }
+    scope :contained_by, ->(value) { where('ip <<= ?', value) }
+  end
+end
diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb
index 5ed4d2a84..416ae3838 100644
--- a/app/models/ip_block.rb
+++ b/app/models/ip_block.rb
@@ -17,6 +17,7 @@ class IpBlock < ApplicationRecord
   CACHE_KEY = 'blocked_ips'
 
   include Expireable
+  include InetContainer
   include Paginable
 
   enum :severity, {
diff --git a/app/models/user.rb b/app/models/user.rb
index 8827a8fbd..9a215669b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -125,7 +125,7 @@ class User < ApplicationRecord
   scope :signed_in_recently, -> { where(current_sign_in_at: ACTIVE_DURATION.ago..) }
   scope :not_signed_in_recently, -> { where(current_sign_in_at: ...ACTIVE_DURATION.ago) }
   scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
-  scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') }
+  scope :matches_ip, ->(value) { left_joins(:ips).merge(IpBlock.contained_by(value)).group('users.id') }
 
   before_validation :sanitize_role
   before_create :set_approved
@@ -444,7 +444,7 @@ class User < ApplicationRecord
   end
 
   def sign_up_from_ip_requires_approval?
-    sign_up_ip.present? && IpBlock.severity_sign_up_requires_approval.exists?(['ip >>= ?', sign_up_ip.to_s])
+    sign_up_ip.present? && IpBlock.severity_sign_up_requires_approval.containing(sign_up_ip.to_s).exists?
   end
 
   def sign_up_email_requires_approval?
diff --git a/app/models/user_ip.rb b/app/models/user_ip.rb
index a6da2c074..25aa81ccd 100644
--- a/app/models/user_ip.rb
+++ b/app/models/user_ip.rb
@@ -11,6 +11,7 @@
 
 class UserIp < ApplicationRecord
   include DatabaseViewRecord
+  include InetContainer
 
   self.primary_key = :user_id
 
diff --git a/lib/mastodon/cli/ip_blocks.rb b/lib/mastodon/cli/ip_blocks.rb
index ef24f2e04..f1f40c99c 100644
--- a/lib/mastodon/cli/ip_blocks.rb
+++ b/lib/mastodon/cli/ip_blocks.rb
@@ -80,9 +80,9 @@ module Mastodon::CLI
         end
 
         ip_blocks = if options[:force]
-                      IpBlock.where('ip >>= ?', address)
+                      IpBlock.containing(address)
                     else
-                      IpBlock.where('ip <<= ?', address)
+                      IpBlock.contained_by(address)
                     end
 
         if ip_blocks.empty?