diff --git a/app/logical/danbooru/ip_address.rb b/app/logical/danbooru/ip_address.rb index 7e887fbcb..1fe36baa6 100644 --- a/app/logical/danbooru/ip_address.rb +++ b/app/logical/danbooru/ip_address.rb @@ -6,11 +6,11 @@ module Danbooru class IpAddress attr_reader :ip_address - delegate :ipv4?, :ipv6?, :loopback?, :link_local?, :unique_local?, :private?, :to_string, :prefix, :multicast?, :unspecified?, to: :ip_address + delegate :ipv4?, :ipv6?, :loopback?, :link_local?, :unique_local?, :private?, :to_string, :network, :prefix, :multicast?, :unspecified?, to: :ip_address delegate :ip_info, :is_proxy?, to: :ip_lookup def initialize(string) - @ip_address = ::IPAddress.parse(string.to_s) + @ip_address = ::IPAddress.parse(string.to_s.strip) end def ip_lookup @@ -39,9 +39,13 @@ module Danbooru ip_address.include?(other.ip_address) end + def as_json + to_s + end + # "1.2.3.4/24" if the address is a subnet, "1.2.3.4" otherwise. def to_s - ip_address.size > 1 ? ip_address.to_string : ip_address.to_s + ip_address.size > 1 ? "#{network}/#{prefix}" : ip_address.to_s end def inspect diff --git a/app/logical/ip_address_type.rb b/app/logical/ip_address_type.rb index f947c1e2e..16afdb041 100644 --- a/app/logical/ip_address_type.rb +++ b/app/logical/ip_address_type.rb @@ -18,6 +18,8 @@ class IpAddressType < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Inet def cast(value) return nil if value.blank? super(Danbooru::IpAddress.new(value)) + rescue ArgumentError + nil end # Serialize a Danbooru::IpAddress to a String for the database. diff --git a/app/models/ip_ban.rb b/app/models/ip_ban.rb index aebd3e8c1..729a80082 100644 --- a/app/models/ip_ban.rb +++ b/app/models/ip_ban.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class IpBan < ApplicationRecord + attribute :ip_addr, :ip_address + belongs_to :creator, class_name: "User" validate :validate_ip_addr @@ -23,7 +25,7 @@ class IpBan < ApplicationRecord end def self.ip_matches(ip_addr) - where("ip_addr >>= ?", ip_addr) + where("ip_addr >>= ?", ip_addr.to_s) end def self.hit!(category, ip_addr) @@ -62,7 +64,7 @@ class IpBan < ApplicationRecord def validate_ip_addr if ip_addr.blank? errors.add(:ip_addr, "is invalid") - elsif ip_addr.private? || ip_addr.loopback? || ip_addr.link_local? + elsif ip_addr.is_local? errors.add(:ip_addr, "must be a public address") elsif full_ban? && ip_addr.ipv4? && ip_addr.prefix < 24 errors.add(:ip_addr, "may not have a subnet bigger than /24") @@ -72,25 +74,11 @@ class IpBan < ApplicationRecord errors.add(:ip_addr, "may not have a subnet bigger than /48") elsif partial_ban? && ip_addr.ipv6? && ip_addr.prefix < 20 errors.add(:ip_addr, "may not have a subnet bigger than /20") - elsif new_record? && IpBan.active.where(category: category).ip_matches(subnetted_ip).exists? + elsif new_record? && IpBan.active.where(category: category).ip_matches(ip_addr).exists? errors.add(:ip_addr, "is already banned") end end - def has_subnet? - (ip_addr.ipv4? && ip_addr.prefix < 32) || (ip_addr.ipv6? && ip_addr.prefix < 128) - end - - def subnetted_ip - str = ip_addr.to_s - str += "/" + ip_addr.prefix.to_s if has_subnet? - str - end - - def ip_addr=(ip_addr) - super(ip_addr.strip) - end - def self.available_includes [:creator] end diff --git a/app/models/user.rb b/app/models/user.rb index e81747f76..8d069c525 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -67,7 +67,7 @@ class User < ApplicationRecord attribute :inviter_id attribute :last_logged_in_at, default: -> { Time.zone.now } attribute :last_forum_read_at, default: "1960-01-01 00:00:00" - attribute :last_ip_addr + attribute :last_ip_addr, :ip_address attribute :comment_threshold, default: -8 attribute :default_image_size, default: "large" attribute :favorite_tags diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index bda7be4d0..e8980dc19 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -85,8 +85,7 @@ class ApplicationPolicy # The list of attributes that are permitted to be returned by the API. def api_attributes - # XXX allow inet - record.class.attribute_types.reject { |_name, attr| attr.type.in?([:inet, :tsvector]) }.keys.map(&:to_sym) + record.class.column_names.map(&:to_sym) end # The list of attributes that are permitted to be used as data-* attributes diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index e26673c46..6183ab4ab 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -72,6 +72,8 @@ class UserPolicy < ApplicationPolicy ] end + attributes += [:last_ip_addr] if policy(:ip_address).show? + attributes end diff --git a/app/views/ip_bans/index.html.erb b/app/views/ip_bans/index.html.erb index 49b3276f4..32ffe62ca 100644 --- a/app/views/ip_bans/index.html.erb +++ b/app/views/ip_bans/index.html.erb @@ -14,7 +14,7 @@ <%= table_for @ip_bans, class: "striped autofit", width: "100%" do |t| %> <% t.column "IP Address" do |ip_ban| %> - <%= link_to ip_ban.subnetted_ip, ip_address_path(ip_ban.ip_addr.to_s) %> + <%= link_to ip_ban.ip_addr, ip_address_path(ip_ban.ip_addr.to_s) %> <% end %> <% t.column "Reason", td: { class: "col-expand" } do |ban| %>
diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index 2771aa7f5..55c574ac6 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -127,21 +127,28 @@ class UsersControllerTest < ActionDispatch::IntegrationTest end should "show hidden attributes to the owner" do - get_auth user_path(@user), @user, params: {format: :json} - json = JSON.parse(response.body) + get_auth user_path(@user), @user, as: :json assert_response :success - assert_not_nil(json["last_logged_in_at"]) + assert_not_nil(response.parsed_body["last_logged_in_at"]) + end + + should "show the last_ip_addr to mods" do + user = create(:user, last_ip_addr: "1.2.3.4") + get_auth user_path(user), create(:mod_user), as: :json + + assert_response :success + assert_equal("1.2.3.4", response.parsed_body["last_ip_addr"]) end should "not show hidden attributes to others" do @another = create(:user) - get_auth user_path(@another), @user, params: {format: :json} - json = JSON.parse(response.body) + get_auth user_path(@another), @user, as: :json assert_response :success - assert_nil(json["last_logged_in_at"]) + assert_nil(response.parsed_body["last_logged_in_at"]) + assert_nil(response.parsed_body["last_ip_addr"]) end should "strip '?' from attributes" do diff --git a/test/unit/ip_ban_test.rb b/test/unit/ip_ban_test.rb index af211ce83..8e4ea3468 100644 --- a/test/unit/ip_ban_test.rb +++ b/test/unit/ip_ban_test.rb @@ -4,14 +4,14 @@ class IpBanTest < ActiveSupport::TestCase should "be able to ban a user" do ip_ban = create(:ip_ban, ip_addr: "1.2.3.4") - assert_equal("1.2.3.4", ip_ban.subnetted_ip) + assert_equal("1.2.3.4", ip_ban.ip_addr.to_s) assert(IpBan.ip_matches("1.2.3.4").exists?) end should "be able to ban a subnet" do ip_ban = create(:ip_ban, ip_addr: "1.2.3.4/24") - assert_equal("1.2.3.0/24", ip_ban.subnetted_ip) + assert_equal("1.2.3.0/24", ip_ban.ip_addr.to_s) assert(IpBan.ip_matches("1.2.3.0").exists?) assert(IpBan.ip_matches("1.2.3.255").exists?) end