api: make IP addresses in the API.
Make the following fields visible in API responses: * ip_bans.ip_addr * ip_geolocations.ip_addr * ip_geolocations.network * users.last_ip_addr (mod only) * user_sessions.ip_addr * api_keys.last_ip_address * api_keys.permitted_ip_addresses Before IP addresses were globally hidden in API responses because IPs were present in a lot of tables and we didn't want to accidentally leak them. Now that we've gotten rid of IPs from most tables, it's safe to unhide them.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -72,6 +72,8 @@ class UserPolicy < ApplicationPolicy
|
||||
]
|
||||
end
|
||||
|
||||
attributes += [:last_ip_addr] if policy(:ip_address).show?
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
|
||||
@@ -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| %>
|
||||
<div class="prose">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user