diff --git a/app/concerns/mdm/workspace/boundary_range.rb b/app/concerns/mdm/workspace/boundary_range.rb new file mode 100644 index 0000000000..ee68038362 --- /dev/null +++ b/app/concerns/mdm/workspace/boundary_range.rb @@ -0,0 +1,60 @@ +module Mdm::Workspace::BoundaryRange + extend ActiveSupport::Concern + + included do + # + # Validations + # + + validate :boundary_must_be_ip_range + + # + # Instance Methods + # + + # If {#limit_to_network} is disabled, this will always return `true`. + # Otherwise, return `true` only if all of the given IPs are within the + # project {#boundary boundaries}. + + # + # @param ips [String] IP range(s) + # @return [true] if actions on ips are allowed. + # @return [false] if actions are not allowed on ips. + def allow_actions_on?(ips) + return true unless limit_to_network + return true unless boundary + return true if boundary.empty? + boundaries = Shellwords.split(boundary) + return true if boundaries.empty? # It's okay if there is no boundary range after all + given_range = Rex::Socket::RangeWalker.new(ips) + return false unless given_range # Can't do things to nonexistant IPs + allowed = false + boundaries.each do |boundary_range| + ok_range = Rex::Socket::RangeWalker.new(boundary) + allowed = true if ok_range.include_range? given_range + end + return allowed + end + + # Validates that {#boundary} is {#valid_ip_or_range? a valid IP address or + # IP address range}. Due to this not being tested before it was moved here + # from Mdm, the default workspace does not validate. We therefore don't + # validate boundaries of workspaces that don't use them. + # + # @return [void] + def boundary_must_be_ip_range + errors.add(:boundary, "must be a valid IP range") unless !limit_to_network || valid_ip_or_range?(boundary) + end + + private + + # Returns whether `string` is a valid IP address or IP address range. + # + # @return [true] if valid IP address or IP address range. + # @return [false] otherwise. + def valid_ip_or_range?(string) + range = Rex::Socket::RangeWalker.new(string) + range && range.ranges && range.ranges.any? + end + end +end diff --git a/spec/models/mdm/workspace_spec.rb b/spec/models/mdm/workspace_spec.rb new file mode 100644 index 0000000000..dafbf541d4 --- /dev/null +++ b/spec/models/mdm/workspace_spec.rb @@ -0,0 +1,73 @@ +RSpec.describe Mdm::Workspace, type: :model do + subject(:workspace) do + Mdm::Workspace.new + end + + context 'validations' do + context 'boundary' do + let(:boundary) do + nil + end + + let(:error) do + 'must be a valid IP range' + end + + context 'when the workspace is limited to a network' do + before(:example) do + workspace.boundary = boundary + workspace.limit_to_network = true + workspace.valid? + end + + it 'should validate using #valid_ip_or_range?' do + expect(workspace).to receive(:valid_ip_or_range?).with(boundary).and_return(false) + + workspace.valid? + end + + context 'with valid IP' do + let(:boundary) do + '192.168.0.1' + end + + it 'should not record an error' do + expect(workspace.errors[:boundary]).not_to include(error) + end + end + + context 'with valid range' do + let(:boundary) do + '192.168.0.1/24' + end + + it 'should not record an error' do + expect(workspace.errors[:boundary]).not_to include(error) + end + end + + context 'with invalid IP or range' do + let(:boundary) do + '192.168' + end + + it 'should record error that boundary must be a valid IP range' do + expect(workspace).not_to be_valid + expect(workspace.errors[:boundary]).to include(error) + end + end + end + + context 'when the workspace is not network limited' do + before(:example) do + workspace.boundary = boundary + workspace.valid? + end + + it 'should not care about the value of the boundary' do + expect(workspace.errors[:boundary]).not_to include(error) + end + end + end + end +end