~cytrogen/masto-fe

ref: 6dbd44faea49ad44d26ee9fa9007ee0fca90dbbc masto-fe/lib/mastodon/cli/ip_blocks.rb -rw-r--r-- 4.3 KiB
6dbd44fa — Claire Merge commit 'b896b16cb3c8626fbee12a7eda7f882114b1a040' into glitch-soc/merge-upstream 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# frozen_string_literal: true

require 'rubygems/package'
require_relative '../../../config/boot'
require_relative '../../../config/environment'
require_relative 'helper'

module Mastodon::CLI
  class IpBlocks < Thor
    def self.exit_on_failure?
      true
    end

    option :severity, required: true, enum: %w(no_access sign_up_requires_approval sign_up_block), desc: 'Severity of the block'
    option :comment, aliases: [:c], desc: 'Optional comment'
    option :duration, aliases: [:d], type: :numeric, desc: 'Duration of the block in seconds'
    option :force, type: :boolean, aliases: [:f], desc: 'Overwrite existing blocks'
    desc 'add IP...', 'Add one or more IP blocks'
    long_desc <<-LONG_DESC
      Add one or more IP blocks. You can use CIDR syntax to
      block IP ranges. You must specify --severity of the block. All
      options will be copied for each IP block you create in one command.

      You can add a --comment. If an IP block already exists for one of
      the provided IPs, it will be skipped unless you use the --force
      option to overwrite it.
    LONG_DESC
    def add(*addresses)
      if addresses.empty?
        say('No IP(s) given', :red)
        exit(1)
      end

      skipped   = 0
      processed = 0
      failed    = 0

      addresses.each do |address|
        unless valid_ip_address?(address)
          say("#{address} is invalid", :red)
          failed += 1
          next
        end

        ip_block = IpBlock.find_by(ip: address)

        if ip_block.present? && !options[:force]
          say("#{address} is already blocked", :yellow)
          skipped += 1
          next
        end

        ip_block ||= IpBlock.new(ip: address)

        ip_block.severity   = options[:severity]
        ip_block.comment    = options[:comment] if options[:comment].present?
        ip_block.expires_in = options[:duration]

        if ip_block.save
          processed += 1
        else
          say("#{address} could not be saved", :red)
          failed += 1
        end
      end

      say("Added #{processed}, skipped #{skipped}, failed #{failed}", color(processed, failed))
    end

    option :force, type: :boolean, aliases: [:f], desc: 'Remove blocks for ranges that cover given IP(s)'
    desc 'remove IP...', 'Remove one or more IP blocks'
    long_desc <<-LONG_DESC
      Remove one or more IP blocks. Normally, only exact matches are removed. If
      you want to ensure that all of the given IP addresses are unblocked, you
      can use --force which will also remove any blocks for IP ranges that would
      cover the given IP(s).
    LONG_DESC
    def remove(*addresses)
      if addresses.empty?
        say('No IP(s) given', :red)
        exit(1)
      end

      processed = 0
      skipped   = 0

      addresses.each do |address|
        unless valid_ip_address?(address)
          say("#{address} is invalid", :yellow)
          skipped += 1
          next
        end

        ip_blocks = if options[:force]
                      IpBlock.where('ip >>= ?', address)
                    else
                      IpBlock.where('ip <<= ?', address)
                    end

        if ip_blocks.empty?
          say("#{address} is not yet blocked", :yellow)
          skipped += 1
          next
        end

        ip_blocks.in_batches.destroy_all
        processed += 1
      end

      say("Removed #{processed}, skipped #{skipped}", color(processed, 0))
    end

    option :format, aliases: [:f], enum: %w(plain nginx), desc: 'Format of the output'
    desc 'export', 'Export blocked IPs'
    long_desc <<-LONG_DESC
      Export blocked IPs. Different formats are supported for usage with other
      tools. Only blocks with no_access severity are returned.
    LONG_DESC
    def export
      IpBlock.where(severity: :no_access).find_each do |ip_block|
        case options[:format]
        when 'nginx'
          say "deny #{ip_block.ip}/#{ip_block.ip.prefix};"
        else
          say "#{ip_block.ip}/#{ip_block.ip.prefix}"
        end
      end
    end

    private

    def color(processed, failed)
      if !processed.zero? && failed.zero?
        :green
      elsif failed.zero?
        :yellow
      else
        :red
      end
    end

    def valid_ip_address?(ip_address)
      IPAddr.new(ip_address)
      true
    rescue IPAddr::InvalidAddressError
      false
    end
  end
end