2023-05-11 08:19:24 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'rails_helper'
|
2023-05-23 14:08:26 +00:00
|
|
|
require 'mastodon/cli/ip_blocks'
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2024-09-04 05:12:25 +00:00
|
|
|
RSpec.describe Mastodon::CLI::IpBlocks do
|
2023-12-07 13:49:14 +00:00
|
|
|
subject { cli.invoke(action, arguments, options) }
|
|
|
|
|
2023-05-11 08:19:24 +00:00
|
|
|
let(:cli) { described_class.new }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { [] }
|
|
|
|
let(:options) { {} }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-01 12:00:34 +00:00
|
|
|
it_behaves_like 'CLI Command'
|
2023-05-24 09:55:40 +00:00
|
|
|
|
2023-05-11 08:19:24 +00:00
|
|
|
describe '#add' do
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:action) { :add }
|
2023-05-11 08:19:24 +00:00
|
|
|
let(:ip_list) do
|
|
|
|
[
|
|
|
|
'192.0.2.1',
|
|
|
|
'172.16.0.1',
|
|
|
|
'192.0.2.0/24',
|
|
|
|
'172.16.0.0/16',
|
|
|
|
'10.0.0.0/8',
|
|
|
|
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
|
|
|
'fe80::1',
|
|
|
|
'::1',
|
|
|
|
'2001:0db8::/32',
|
|
|
|
'fe80::/10',
|
|
|
|
'::/128',
|
|
|
|
]
|
|
|
|
end
|
|
|
|
let(:options) { { severity: 'no_access' } }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { ip_list }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
|
|
|
shared_examples 'ip address blocking' do
|
2023-12-11 10:23:45 +00:00
|
|
|
def blocked_ip_addresses
|
|
|
|
IpBlock.where(ip: ip_list).pluck(:ip)
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
def expected_ip_addresses
|
|
|
|
ip_list.map { |ip| IPAddr.new(ip) }
|
|
|
|
end
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
def blocked_ips_severity
|
|
|
|
IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'blocks and sets severity for ip address and displays summary' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("Added #{ip_list.size}, skipped 0, failed 0")
|
2023-12-11 10:23:45 +00:00
|
|
|
expect(blocked_ip_addresses)
|
|
|
|
.to match_array(expected_ip_addresses)
|
|
|
|
expect(blocked_ips_severity)
|
|
|
|
.to be(true)
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with valid IP addresses' do
|
|
|
|
include_examples 'ip address blocking'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a specified IP address is already blocked' do
|
|
|
|
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { ip_list }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
before { allow(IpBlock).to receive(:new).and_call_original }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'skips already block ip and displays the correct summary' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
|
2023-12-11 10:23:45 +00:00
|
|
|
|
|
|
|
expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last)
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'with --force option' do
|
|
|
|
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') }
|
|
|
|
let(:options) { { severity: 'sign_up_requires_approval', force: true } }
|
|
|
|
|
|
|
|
it 'overwrites the existing IP block record' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
2023-12-13 10:14:19 +00:00
|
|
|
.to output_results('Added 11')
|
|
|
|
.and change { blocked_ip.reload.severity }
|
2023-05-11 08:19:24 +00:00
|
|
|
.from('no_access')
|
|
|
|
.to('sign_up_requires_approval')
|
|
|
|
end
|
|
|
|
|
|
|
|
include_examples 'ip address blocking'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a specified IP address is invalid' do
|
|
|
|
let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { ip_list }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
|
|
|
it 'displays the correct summary' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with --comment option' do
|
|
|
|
let(:options) { { severity: 'no_access', comment: 'Spam' } }
|
|
|
|
|
|
|
|
include_examples 'ip address blocking'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with --duration option' do
|
|
|
|
let(:options) { { severity: 'no_access', duration: 10.days } }
|
|
|
|
|
|
|
|
include_examples 'ip address blocking'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with "sign_up_requires_approval" severity' do
|
|
|
|
let(:options) { { severity: 'sign_up_requires_approval' } }
|
|
|
|
|
|
|
|
include_examples 'ip address blocking'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with "sign_up_block" severity' do
|
|
|
|
let(:options) { { severity: 'sign_up_block' } }
|
|
|
|
|
|
|
|
include_examples 'ip address blocking'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a specified IP address fails to be blocked' do
|
|
|
|
let(:ip_address) { '127.0.0.1' }
|
|
|
|
let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { [ip_address] }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
|
|
|
before do
|
|
|
|
allow(IpBlock).to receive(:new).and_return(ip_block)
|
|
|
|
allow(ip_block).to receive(:severity=)
|
|
|
|
allow(ip_block).to receive(:expires_in=)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'displays an error message' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("#{ip_address} could not be saved")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when no IP address is provided' do
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { [] }
|
|
|
|
|
2023-05-11 08:19:24 +00:00
|
|
|
it 'exits with an error message' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
2024-01-26 08:53:44 +00:00
|
|
|
.to raise_error(Thor::Error, 'No IP(s) given')
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#remove' do
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:action) { :remove }
|
|
|
|
|
2023-05-11 08:19:24 +00:00
|
|
|
context 'when removing exact matches' do
|
|
|
|
let(:ip_list) do
|
|
|
|
[
|
|
|
|
'192.0.2.1',
|
|
|
|
'172.16.0.1',
|
|
|
|
'192.0.2.0/24',
|
|
|
|
'172.16.0.0/16',
|
|
|
|
'10.0.0.0/8',
|
|
|
|
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
|
|
|
'fe80::1',
|
|
|
|
'::1',
|
|
|
|
'2001:0db8::/32',
|
|
|
|
'fe80::/10',
|
|
|
|
'::/128',
|
|
|
|
]
|
|
|
|
end
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { ip_list }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
|
|
|
before do
|
|
|
|
ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) }
|
|
|
|
end
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'removes exact ip blocks and displays success message with a summary' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("Removed #{ip_list.size}, skipped 0")
|
2023-12-11 10:23:45 +00:00
|
|
|
expect(IpBlock.where(ip: ip_list)).to_not exist
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with --force option' do
|
2023-06-14 14:44:37 +00:00
|
|
|
let!(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
|
|
|
|
let!(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
|
|
|
|
let!(:third_ip_range_block) { IpBlock.create(ip: '172.16.0.0/20', severity: :no_access) }
|
2023-05-11 08:19:24 +00:00
|
|
|
let(:arguments) { ['192.168.0.5', '10.0.1.50'] }
|
|
|
|
let(:options) { { force: true } }
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'removes blocks for IP ranges that cover given IP(s) and keeps other ranges' do
|
2023-12-13 10:14:19 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results('Removed 2')
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
expect(covered_ranges).to_not exist
|
|
|
|
expect(other_ranges).to exist
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
def covered_ranges
|
|
|
|
IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])
|
|
|
|
end
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
def other_ranges
|
|
|
|
IpBlock.where(id: third_ip_range_block.id)
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a specified IP address is not blocked' do
|
|
|
|
let(:unblocked_ip) { '192.0.2.1' }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { [unblocked_ip] }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'skips the IP address and displays summary' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
2023-12-11 10:23:45 +00:00
|
|
|
.to output_results(
|
|
|
|
"#{unblocked_ip} is not yet blocked",
|
|
|
|
'Removed 0, skipped 1'
|
|
|
|
)
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a specified IP address is invalid' do
|
|
|
|
let(:invalid_ip) { '320.15.175.0' }
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:arguments) { [invalid_ip] }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'skips the invalid IP address and displays summary' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
2023-12-11 10:23:45 +00:00
|
|
|
.to output_results(
|
|
|
|
"#{invalid_ip} is invalid",
|
|
|
|
'Removed 0, skipped 1'
|
|
|
|
)
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when no IP address is provided' do
|
|
|
|
it 'exits with an error message' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
2024-01-26 08:53:44 +00:00
|
|
|
.to raise_error(Thor::Error, 'No IP(s) given')
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#export' do
|
2023-12-07 13:49:14 +00:00
|
|
|
let(:action) { :export }
|
|
|
|
|
2023-06-14 14:44:37 +00:00
|
|
|
let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
|
|
|
|
let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
|
|
|
|
let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) }
|
2023-05-11 08:19:24 +00:00
|
|
|
|
|
|
|
context 'when --format option is set to "plain"' do
|
|
|
|
let(:options) { { format: 'plain' } }
|
|
|
|
|
|
|
|
it 'exports blocked IPs with "no_access" severity in plain format' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'does not export blocked IPs with different severities' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when --format option is set to "nginx"' do
|
|
|
|
let(:options) { { format: 'nginx' } }
|
|
|
|
|
|
|
|
it 'exports blocked IPs with "no_access" severity in plain format' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
|
2023-12-11 10:23:45 +00:00
|
|
|
it 'does not export blocked IPs with different severities' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when --format option is not provided' do
|
|
|
|
it 'exports blocked IPs in plain format by default' do
|
2023-12-07 13:49:14 +00:00
|
|
|
expect { subject }
|
|
|
|
.to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
|
2023-05-11 08:19:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|