Land #6405, prefer default module base class of simply 'Metasploit'
commit
a2c3b05416
|
@ -56,7 +56,7 @@ module Exploit::Remote::FtpServer
|
|||
# exists for the given command, returns a generic default response.
|
||||
#
|
||||
# @example Handle SYST requests
|
||||
# class Metasploit4 < Msf::Exploit
|
||||
# class Metasploit < Msf::Exploit
|
||||
# include Msf::Exploit::Remote::FtpServer
|
||||
# ...
|
||||
# def on_client_command_syst(cmd_conn, arg)
|
||||
|
|
|
@ -17,7 +17,7 @@ module Msf
|
|||
# @example Use it from an Auxiliary module
|
||||
# require 'msf/core'
|
||||
#
|
||||
# class Metasploit3 < Msf::Auxiliary
|
||||
# class Metasploit < Msf::Auxiliary
|
||||
#
|
||||
# include Msf::Exploit::Remote::SMB::Server::Share
|
||||
#
|
||||
|
@ -59,7 +59,7 @@ module Msf
|
|||
# @example Use it from an Exploit module
|
||||
# require 'msf/core'
|
||||
#
|
||||
# class Metasploit3 < Msf::Exploit::Remote
|
||||
# class Metasploit < Msf::Exploit::Remote
|
||||
# Rank = ExcellentRanking
|
||||
#
|
||||
# include Msf::Exploit::EXE
|
||||
|
|
|
@ -120,6 +120,7 @@ module Msf
|
|||
self.module_info_by_path = {}
|
||||
self.enablement_by_type = {}
|
||||
self.module_load_error_by_path = {}
|
||||
self.module_load_warnings = {}
|
||||
self.module_paths = []
|
||||
self.module_set_by_type = {}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ module Msf::ModuleManager::Loading
|
|||
changed
|
||||
end
|
||||
|
||||
attr_accessor :module_load_error_by_path
|
||||
attr_accessor :module_load_error_by_path, :module_load_warnings
|
||||
|
||||
# Called when a module is initially loaded such that it can be categorized
|
||||
# accordingly.
|
||||
|
@ -122,4 +122,4 @@ module Msf::ModuleManager::Loading
|
|||
|
||||
count_by_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
# Project
|
||||
#
|
||||
require 'msf/core/modules/loader'
|
||||
require 'msf/core/modules/namespace'
|
||||
require 'msf/core/modules/metasploit_class_compatibility_error'
|
||||
require 'msf/core/modules/version_compatibility_error'
|
||||
require 'msf/core/modules/error'
|
||||
|
||||
# Responsible for loading modules for {Msf::ModuleManager}.
|
||||
#
|
||||
|
@ -30,9 +28,6 @@ class Msf::Modules::Loader::Base
|
|||
# By calling module_eval from inside the module definition, the lexical scope is captured and available to the code in
|
||||
# module_content.
|
||||
NAMESPACE_MODULE_CONTENT = <<-EOS
|
||||
# ensure the namespace module can respond to checks during loading
|
||||
extend Msf::Modules::Namespace
|
||||
|
||||
class << self
|
||||
# The loader that originally loaded this module
|
||||
#
|
||||
|
@ -103,12 +98,9 @@ class Msf::Modules::Loader::Base
|
|||
# @option options [Boolean] :reload (false) whether this is a reload.
|
||||
#
|
||||
# @return [false] if :force is false and parent_path has not changed.
|
||||
# @return [false] if exception encountered while parsing module
|
||||
# content
|
||||
# @return [false] if the module is incompatible with the Core or API
|
||||
# version.
|
||||
# @return [false] if the module does not implement a Metasploit(\d+)
|
||||
# class.
|
||||
# @return [false] if exception encountered while parsing module content
|
||||
# @return [false] if the module is incompatible with the Core or API version.
|
||||
# @return [false] if the module does not implement a Metasploit class.
|
||||
# @return [false] if the module's is_usable method returns false.
|
||||
# @return [true] if all those condition pass and the module is
|
||||
# successfully loaded.
|
||||
|
@ -131,8 +123,6 @@ class Msf::Modules::Loader::Base
|
|||
|
||||
reload ||= force || file_changed
|
||||
|
||||
metasploit_class = nil
|
||||
|
||||
module_content = read_module_content(parent_path, type, module_reference_name)
|
||||
|
||||
if module_content.empty?
|
||||
|
@ -140,6 +130,7 @@ class Msf::Modules::Loader::Base
|
|||
return false
|
||||
end
|
||||
|
||||
klass = nil
|
||||
try_eval_module = lambda { |namespace_module|
|
||||
# set the parent_path so that the module can be reloaded with #load_module
|
||||
namespace_module.parent_path = parent_path
|
||||
|
@ -150,41 +141,24 @@ class Msf::Modules::Loader::Base
|
|||
rescue ::Interrupt
|
||||
raise
|
||||
rescue ::Exception => error
|
||||
# Hide eval errors when the module version is not compatible
|
||||
begin
|
||||
namespace_module.version_compatible!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
|
||||
load_error(module_path, version_compatibility_error)
|
||||
else
|
||||
load_error(module_path, error)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
begin
|
||||
namespace_module.version_compatible!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
|
||||
load_error(module_path, version_compatibility_error)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
begin
|
||||
metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::MetasploitClassCompatibilityError => error
|
||||
load_error(module_path, error)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
unless usable?(metasploit_class)
|
||||
ilog(
|
||||
"Skipping module (#{module_reference_name} from #{module_path}) because is_usable returned false.",
|
||||
'core',
|
||||
LEV_1
|
||||
)
|
||||
|
||||
if namespace_module.const_defined?('Metasploit3', false)
|
||||
klass = namespace_module.const_get('Metasploit3', false)
|
||||
load_warning(module_path, 'Please change the modules class name from Metasploit3 to Metasploit')
|
||||
elsif namespace_module.const_defined?('Metasploit4', false)
|
||||
klass = namespace_module.const_get('Metasploit4', false)
|
||||
load_warning(module_path, 'Please change the modules class name from Metasploit4 to Metasploit')
|
||||
elsif namespace_module.const_defined?('Metasploit', false)
|
||||
klass = namespace_module.const_get('Metasploit', false)
|
||||
else
|
||||
load_error(module_path, Msf::Modules::Error.new({
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name,
|
||||
:causal_message => 'Invalid module (no Metasploit class or module name)'
|
||||
}))
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -206,7 +180,7 @@ class Msf::Modules::Loader::Base
|
|||
|
||||
# Do some processing on the loaded module to get it into the right associations
|
||||
module_manager.on_module_load(
|
||||
metasploit_class,
|
||||
klass,
|
||||
type,
|
||||
module_reference_name,
|
||||
{
|
||||
|
@ -339,9 +313,9 @@ class Msf::Modules::Loader::Base
|
|||
|
||||
protected
|
||||
|
||||
# Returns a nested module to wrap the Metasploit(1|2|3) class so that it doesn't overwrite other (metasploit)
|
||||
# module's classes. The wrapper module must be named so that active_support's autoloading code doesn't break when
|
||||
# searching constants from inside the Metasploit(1|2|3) class.
|
||||
# Returns a nested module to wrap the Metasploit class so that it doesn't overwrite other (metasploit)
|
||||
# module's classes. The wrapper module must be named so that active_support's autoloading code doesn't break when
|
||||
# searching constants from inside the Metasploit class.
|
||||
#
|
||||
# @param namespace_module_names [Array<String>]
|
||||
# {NAMESPACE_MODULE_NAMES} + <derived-constant-safe names>
|
||||
|
@ -351,7 +325,7 @@ class Msf::Modules::Loader::Base
|
|||
# @see NAMESPACE_MODULE_CONTENT
|
||||
def create_namespace_module(namespace_module_names)
|
||||
# In order to have constants defined in Msf resolve without the Msf qualifier in the module_content, the
|
||||
# Module.nesting must resolve for the entire nesting. Module.nesting is strictly lexical, and can't be faked with
|
||||
# Module.nesting must resolve for the entire nesting. Module.nesting is strictly lexical, and can't be faked with
|
||||
# module_eval(&block). (There's actually code in ruby's implementation to stop module_eval from being added to
|
||||
# Module.nesting when using the block syntax.) All this means is the modules have to be declared as a string that
|
||||
# gets module_eval'd.
|
||||
|
@ -432,13 +406,32 @@ class Msf::Modules::Loader::Base
|
|||
log_lines << "#{module_path} failed to load due to the following error:"
|
||||
log_lines << error.class.to_s
|
||||
log_lines << error.to_s
|
||||
log_lines << "Call stack:"
|
||||
log_lines += error.backtrace
|
||||
if error.backtrace
|
||||
log_lines << "Call stack:"
|
||||
log_lines += error.backtrace
|
||||
end
|
||||
|
||||
log_message = log_lines.join("\n")
|
||||
elog(log_message)
|
||||
end
|
||||
|
||||
# Records the load warning to {Msf::ModuleManager::Loading#module_load_warnings} and the log.
|
||||
#
|
||||
# @param [String] module_path Path to the module as returned by {#module_path}.
|
||||
# @param [String] Error message that caused the warning.
|
||||
# @return [void]
|
||||
#
|
||||
# @see #module_path
|
||||
def load_warning(module_path, error)
|
||||
module_manager.module_load_warnings[module_path] = error.to_s
|
||||
|
||||
log_lines = []
|
||||
log_lines << "#{module_path} generated a warning during load:"
|
||||
log_lines << error.to_s
|
||||
log_message = log_lines.join("\n")
|
||||
wlog(log_message)
|
||||
end
|
||||
|
||||
# @return [Msf::ModuleManager] The module manager for which this loader is loading modules.
|
||||
attr_reader :module_manager
|
||||
|
||||
|
@ -455,7 +448,7 @@ class Msf::Modules::Loader::Base
|
|||
raise ::NotImplementedError
|
||||
end
|
||||
|
||||
# Returns whether the path could refer to a module. The path would still need to be loaded in order to check if it
|
||||
# Returns whether the path could refer to a module. The path would still need to be loaded in order to check if it
|
||||
# actually is a valid module.
|
||||
#
|
||||
# @param [String] path to module without the type directory.
|
||||
|
@ -502,8 +495,8 @@ class Msf::Modules::Loader::Base
|
|||
end
|
||||
|
||||
# Returns an Array of names to make a fully qualified module name to
|
||||
# wrap the Metasploit(1|2|3) class so that it doesn't overwrite other
|
||||
# (metasploit) module's classes. Invalid module name characters are
|
||||
# wrap the Metasploit class so that it doesn't overwrite other
|
||||
# (metasploit) module's classes. Invalid module name characters are
|
||||
# escaped by using 'H*' unpacking and prefixing each code with X so
|
||||
# the code remains a valid module name when it starts with a digit.
|
||||
#
|
||||
|
@ -626,28 +619,4 @@ class Msf::Modules::Loader::Base
|
|||
self.class.typed_path(type, module_reference_name)
|
||||
end
|
||||
|
||||
# Returns whether the metasploit_class is usable on the current system. Defer's to metasploit_class's #is_usable if
|
||||
# it is defined.
|
||||
#
|
||||
# @param [Msf::Module] metasploit_class As returned by {Msf::Modules::Namespace#metasploit_class}
|
||||
# @return [false] if metasploit_class.is_usable returns false.
|
||||
# @return [true] if metasploit_class does not respond to is_usable.
|
||||
# @return [true] if metasploit_class.is_usable returns true.
|
||||
def usable?(metasploit_class)
|
||||
# If the module indicates that it is not usable on this system, then we
|
||||
# will not try to use it.
|
||||
usable = false
|
||||
|
||||
if metasploit_class.respond_to? :is_usable
|
||||
begin
|
||||
usable = metasploit_class.is_usable
|
||||
rescue => error
|
||||
elog("Exception caught during is_usable check: #{error}")
|
||||
end
|
||||
else
|
||||
usable = true
|
||||
end
|
||||
|
||||
usable
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/error'
|
||||
|
||||
# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant
|
||||
# with {Msf::Framework::Major} or lower as a number after 'Metasploit', which indicates a compatible Msf::Module.
|
||||
class Msf::Modules::MetasploitClassCompatibilityError < Msf::Modules::Error
|
||||
def initialize(attributes={})
|
||||
super_attributes = {
|
||||
:causal_message => 'Missing compatible Metasploit<major_version> class constant',
|
||||
}.merge(attributes)
|
||||
|
||||
super(super_attributes)
|
||||
end
|
||||
end
|
|
@ -1,76 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'metasploit/framework/api/version'
|
||||
require 'metasploit/framework/core/version'
|
||||
|
||||
# Concern for behavior that all namespace modules that wrap Msf::Modules must support like version checking and
|
||||
# grabbing the version specific-Metasploit* class.
|
||||
module Msf::Modules::Namespace
|
||||
# Returns the Metasploit(3|2|1) class from the module_evalled content.
|
||||
#
|
||||
# @note The module content must be module_evalled into this namespace module before the return of
|
||||
# {#metasploit_class} is valid.
|
||||
#
|
||||
# @return [Msf::Module] if a Metasploit(3|2|1) class exists in this module
|
||||
# @return [nil] if such as class is not defined.
|
||||
def metasploit_class
|
||||
metasploit_class = nil
|
||||
|
||||
::Msf::Framework::Major.downto(1) do |major|
|
||||
# Since we really only care about the deepest namespace, we don't
|
||||
# need to look for parents' constants. However, the "inherit"
|
||||
# parameter for const_defined? only exists after 1.9. If we ever
|
||||
# drop 1.8 support, we can save a few cycles here by passing false
|
||||
# here.
|
||||
if const_defined?("Metasploit#{major}")
|
||||
metasploit_class = const_get("Metasploit#{major}")
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
metasploit_class
|
||||
end
|
||||
|
||||
def metasploit_class!(module_path, module_reference_name)
|
||||
metasploit_class = self.metasploit_class
|
||||
|
||||
unless metasploit_class
|
||||
raise Msf::Modules::MetasploitClassCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name
|
||||
)
|
||||
end
|
||||
|
||||
metasploit_class
|
||||
end
|
||||
|
||||
# Raises an error unless {Msf::Framework::VersionCore} and {Msf::Framework::VersionAPI} meet the minimum required
|
||||
# versions defined in RequiredVersions in the module content.
|
||||
#
|
||||
# @note The module content must be module_evalled into this namespace module using module_eval_with_lexical_scope
|
||||
# before calling {#version_compatible!} is valid.
|
||||
#
|
||||
# @param [String] module_path Path from where the module was read.
|
||||
# @param [String] module_reference_name The canonical name for the module.
|
||||
# @raise [Msf::Modules::VersionCompatibilityError] if RequiredVersion[0] > Msf::Framework::VersionCore or
|
||||
# RequiredVersion[1] > Msf::Framework::VersionApi
|
||||
# @return [void]
|
||||
def version_compatible!(module_path, module_reference_name)
|
||||
if const_defined?(:RequiredVersions)
|
||||
required_versions = const_get(:RequiredVersions)
|
||||
minimum_core_version = Gem::Version.new(required_versions[0].to_s)
|
||||
minimum_api_version = Gem::Version.new(required_versions[1].to_s)
|
||||
|
||||
if (minimum_core_version > Metasploit::Framework::Core::GEM_VERSION ||
|
||||
minimum_api_version > Metasploit::Framework::API::GEM_VERSION)
|
||||
raise Msf::Modules::VersionCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name,
|
||||
:minimum_api_version => minimum_api_version,
|
||||
:minimum_core_version => minimum_core_version
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/error'
|
||||
|
||||
# Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module}
|
||||
# if the API or Core version does not meet the minimum requirements defined in the RequiredVersions constant in the
|
||||
# {Msf::Modules::Loader::Base#read_module_content module content}.
|
||||
class Msf::Modules::VersionCompatibilityError < Msf::Modules::Error
|
||||
# @param [Hash{Symbol => Float}] attributes
|
||||
# @option attributes [Float] :minimum_api_version The minimum {Msf::Framework::VersionAPI} as defined in
|
||||
# RequiredVersions.
|
||||
# @option attributes [Float] :minimum_core_version The minimum {Msf::Framework::VersionCore} as defined in
|
||||
# RequiredVersions.
|
||||
def initialize(attributes={})
|
||||
@minimum_api_version = attributes[:minimum_api_version]
|
||||
@minimum_core_version = attributes[:minimum_core_version]
|
||||
|
||||
message_parts = []
|
||||
message_parts << 'version check'
|
||||
|
||||
if minimum_api_version or minimum_core_version
|
||||
clause_parts = []
|
||||
|
||||
if minimum_api_version
|
||||
clause_parts << "API >= #{minimum_api_version}"
|
||||
end
|
||||
|
||||
if minimum_core_version
|
||||
clause_parts << "Core >= #{minimum_core_version}"
|
||||
end
|
||||
|
||||
clause = clause_parts.join(' and ')
|
||||
message_parts << "(requires #{clause})"
|
||||
end
|
||||
|
||||
causal_message = message_parts.join(' ')
|
||||
|
||||
super_attributes = {
|
||||
:causal_message => causal_message
|
||||
}.merge(attributes)
|
||||
|
||||
super(super_attributes)
|
||||
end
|
||||
|
||||
# @return [Float] The minimum value of {Msf::Framework::VersionAPI} for the module to be compatible.
|
||||
attr_reader :minimum_api_version
|
||||
# @return [Float] The minimum value of {Msf::Framework::VersionCore} for the module to be compatible.
|
||||
attr_reader :minimum_core_version
|
||||
# @return [String] the path to the module that declared the RequiredVersions
|
||||
attr_reader :module_path
|
||||
# @return [String] the module reference name that declared the RequiredVersions
|
||||
attr_reader :module_reference_name
|
||||
end
|
|
@ -224,6 +224,13 @@ class Core
|
|||
end
|
||||
end
|
||||
|
||||
if framework.modules.module_load_warnings.length > 0
|
||||
print_warning("The following modules were loaded with warnings:")
|
||||
framework.modules.module_load_warnings.each do |path, error|
|
||||
print_warning("\t#{path}: #{error}")
|
||||
end
|
||||
end
|
||||
|
||||
cmd_banner()
|
||||
end
|
||||
|
||||
|
|
|
@ -535,6 +535,13 @@ class Driver < Msf::Ui::Driver
|
|||
end
|
||||
end
|
||||
|
||||
if framework.modules.module_load_warnings.length > 0
|
||||
print_warning("The following modules were loaded with warnings:")
|
||||
framework.modules.module_load_warnings.each do |path, error|
|
||||
print_warning("\t#{path}: #{error}")
|
||||
end
|
||||
end
|
||||
|
||||
framework.events.on_ui_start(Msf::Framework::Revision)
|
||||
|
||||
if $msf_spinner_thread
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
class Metasploit < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HTTP::Joomla
|
||||
|
|
|
@ -12,7 +12,7 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
|
||||
let(:malformed_module_content) do
|
||||
<<-EOS
|
||||
class Metasploit3
|
||||
class Metasploit
|
||||
# purposeful typo to check that module path is used in backtrace
|
||||
inclde Exploit::Remote::Tcp
|
||||
end
|
||||
|
@ -21,7 +21,7 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
|
||||
let(:module_content) do
|
||||
<<-EOS
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
class Metasploit < Msf::Auxiliary
|
||||
# fully-qualified name is Msf::GoodRanking, so this will failing if lexical scope is not captured
|
||||
Rank = GoodRanking
|
||||
end
|
||||
|
@ -223,7 +223,7 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
|
||||
context 'instance methods' do
|
||||
let(:module_manager) do
|
||||
double('Module Manager', :module_load_error_by_path => {})
|
||||
double('Module Manager', :module_load_error_by_path => {}, :module_load_warnings => {})
|
||||
end
|
||||
|
||||
subject do
|
||||
|
@ -309,7 +309,7 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
module Msf
|
||||
module Modules
|
||||
module Mod617578696c696172792f72737065632f6d6f636b
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
class Metasploit < Msf::Auxiliary
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -424,51 +424,12 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
allow(error).to receive(:backtrace).and_return(backtrace)
|
||||
end
|
||||
|
||||
context 'with version compatibility' do
|
||||
before(:example) do
|
||||
expect(@namespace_module).to receive(:version_compatible!).with(module_path, module_reference_name)
|
||||
end
|
||||
|
||||
it 'should record the load error using the original error' do
|
||||
expect(subject).to receive(:load_error).with(module_path, error)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'without version compatibility' do
|
||||
let(:version_compatibility_error) do
|
||||
Msf::Modules::VersionCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name,
|
||||
:minimum_api_version => infinity,
|
||||
:minimum_core_version => infinity
|
||||
)
|
||||
end
|
||||
|
||||
let(:infinity) do
|
||||
0.0 / 0.0
|
||||
end
|
||||
|
||||
before(:example) do
|
||||
allow(@namespace_module).to receive(
|
||||
:version_compatible!
|
||||
).with(
|
||||
module_path,
|
||||
module_reference_name
|
||||
).and_raise(
|
||||
version_compatibility_error
|
||||
)
|
||||
end
|
||||
|
||||
it 'should record the load error using the Msf::Modules::VersionCompatibilityError' do
|
||||
expect(subject).to receive(:load_error).with(module_path, version_compatibility_error)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
it 'should record the load error using the original error' do
|
||||
expect(subject).to receive(:load_error).with(module_path, error)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
expect(@namespace_module).to receive(:version_compatible!).with(module_path, module_reference_name)
|
||||
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
@ -479,9 +440,13 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
@namespace_module = double('Namespace Module')
|
||||
allow(@namespace_module).to receive(:parent_path=)
|
||||
allow(@namespace_module).to receive(:module_eval_with_lexical_scope).with(module_content, module_path)
|
||||
|
||||
metasploit_class = double('Metasploit Class', :parent => @namespace_module)
|
||||
allow(@namespace_module).to receive(:metasploit_class!).and_return(metasploit_class)
|
||||
allow(@namespace_module).to receive(:const_defined?).with('Metasploit3', false).and_return(false)
|
||||
allow(@namespace_module).to receive(:const_defined?).with('Metasploit4', false).and_return(false)
|
||||
allow(@namespace_module).to receive(:const_defined?).with('Metasploit', false).and_return(true)
|
||||
allow(@namespace_module).to receive(:const_get).with('Metasploit3', false).and_return(false)
|
||||
allow(@namespace_module).to receive(:const_get).with('Metasploit4', false).and_return(false)
|
||||
allow(@namespace_module).to receive(:const_get).with('Metasploit', false).and_return(true)
|
||||
allow(@namespace_module).to receive(:module_load_warnings)
|
||||
|
||||
allow(subject).to receive(:namespace_module_transaction).and_yield(@namespace_module)
|
||||
|
||||
|
@ -489,210 +454,83 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
|
||||
@module_load_error_by_path = {}
|
||||
allow(module_manager).to receive(:module_load_error_by_path).and_return(@module_load_error_by_path)
|
||||
allow(module_manager).to receive(:on_module_load)
|
||||
# remove the mocked namespace_module since happy-path/real loading is occurring in this context
|
||||
allow(subject).to receive(:namespace_module_transaction).and_call_original
|
||||
end
|
||||
|
||||
it 'should check for version compatibility' do
|
||||
it 'should log load information' do
|
||||
expect(subject).to receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_2)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'should delete any pre-existing load errors from module_manager.module_load_error_by_path' do
|
||||
original_load_error = "Back in my day this module didn't load"
|
||||
module_manager.module_load_error_by_path[module_path] = original_load_error
|
||||
|
||||
expect(module_manager.module_load_error_by_path[module_path]).to eq original_load_error
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
expect(module_manager.module_load_error_by_path[module_path]).to be_nil
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'should call module_manager.on_module_load' do
|
||||
expect(module_manager).to receive(:on_module_load)
|
||||
|
||||
expect(@namespace_module).to receive(:version_compatible!).with(module_path, module_reference_name)
|
||||
subject.load_module(parent_path, type, module_reference_name)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
context 'without version compatibility' do
|
||||
let(:version_compatibility_error) do
|
||||
Msf::Modules::VersionCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name,
|
||||
:minimum_api_version => infinity,
|
||||
:minimum_core_version => infinity
|
||||
)
|
||||
end
|
||||
|
||||
let(:infinity) do
|
||||
0.0 / 0.0
|
||||
end
|
||||
|
||||
before(:example) do
|
||||
allow(@namespace_module).to receive(
|
||||
:version_compatible!
|
||||
).with(
|
||||
module_path,
|
||||
module_reference_name
|
||||
).and_raise(
|
||||
version_compatibility_error
|
||||
)
|
||||
end
|
||||
|
||||
it 'should record the load error' do
|
||||
expect(subject).to receive(:load_error).with(module_path, version_compatibility_error)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should restore the old namespace module' do
|
||||
context 'with :recalculate_by_type' do
|
||||
it 'should set the type to be recalculated' do
|
||||
recalculate_by_type = {}
|
||||
|
||||
expect(
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:recalculate_by_type => recalculate_by_type
|
||||
)
|
||||
).to eq true
|
||||
expect(recalculate_by_type[type]).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with version compatibility' do
|
||||
before(:example) do
|
||||
allow(@namespace_module).to receive(:version_compatible!).with(module_path, module_reference_name)
|
||||
context 'with :count_by_type' do
|
||||
it 'should set the count to 1 if it does not exist' do
|
||||
count_by_type = {}
|
||||
|
||||
allow(module_manager).to receive(:on_module_load)
|
||||
expect(count_by_type.has_key?(type)).to be_falsey
|
||||
expect(
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:count_by_type => count_by_type
|
||||
)
|
||||
).to eq true
|
||||
expect(count_by_type[type]).to eq 1
|
||||
end
|
||||
|
||||
context 'without metasploit_class' do
|
||||
let(:error) do
|
||||
Msf::Modules::MetasploitClassCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name
|
||||
it 'should increment the count if it does exist' do
|
||||
original_count = 1
|
||||
count_by_type = {
|
||||
type => original_count
|
||||
}
|
||||
|
||||
expect(
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:count_by_type => count_by_type
|
||||
)
|
||||
end
|
||||
).to eq true
|
||||
|
||||
before(:example) do
|
||||
expect(@namespace_module).to receive(:metasploit_class!).with(module_path, module_reference_name).and_raise(error)
|
||||
end
|
||||
|
||||
it 'should record load error' do
|
||||
expect(subject).to receive(
|
||||
:load_error
|
||||
).with(
|
||||
module_path,
|
||||
kind_of(Msf::Modules::MetasploitClassCompatibilityError)
|
||||
)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should restore the old namespace module' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
expect(Msf::Modules.const_defined?(relative_name)).to be_truthy
|
||||
expect(Msf::Modules.const_get(relative_name)).to eq @original_namespace_module
|
||||
end
|
||||
end
|
||||
|
||||
context 'with metasploit_class' do
|
||||
let(:metasploit_class) do
|
||||
double('Metasploit Class')
|
||||
end
|
||||
|
||||
before(:example) do
|
||||
allow(@namespace_module).to receive(:metasploit_class!).and_return(metasploit_class)
|
||||
end
|
||||
|
||||
it 'should check if it is usable' do
|
||||
expect(subject).to receive(:usable?).with(metasploit_class).and_return(true)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
context 'without usable metasploit_class' do
|
||||
before(:example) do
|
||||
expect(subject).to receive(:usable?).and_return(false)
|
||||
end
|
||||
|
||||
it 'should log information' do
|
||||
expect(subject).to receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_1)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'should restore the old namespace module' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_falsey
|
||||
expect(Msf::Modules.const_defined?(relative_name)).to be_truthy
|
||||
expect(Msf::Modules.const_get(relative_name)).to eq @original_namespace_module
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usable metasploit_class' do
|
||||
before(:example) do
|
||||
# remove the mocked namespace_module since happy-path/real loading is occurring in this context
|
||||
allow(subject).to receive(:namespace_module_transaction).and_call_original
|
||||
end
|
||||
|
||||
it 'should log load information' do
|
||||
expect(subject).to receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_2)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'should delete any pre-existing load errors from module_manager.module_load_error_by_path' do
|
||||
original_load_error = "Back in my day this module didn't load"
|
||||
module_manager.module_load_error_by_path[module_path] = original_load_error
|
||||
|
||||
expect(module_manager.module_load_error_by_path[module_path]).to eq original_load_error
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
expect(module_manager.module_load_error_by_path[module_path]).to be_nil
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'should call module_manager.on_module_load' do
|
||||
expect(module_manager).to receive(:on_module_load)
|
||||
expect(subject.load_module(parent_path, type, module_reference_name)).to be_truthy
|
||||
end
|
||||
|
||||
context 'with :recalculate_by_type' do
|
||||
it 'should set the type to be recalculated' do
|
||||
recalculate_by_type = {}
|
||||
|
||||
expect(
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:recalculate_by_type => recalculate_by_type
|
||||
)
|
||||
).to eq true
|
||||
expect(recalculate_by_type[type]).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :count_by_type' do
|
||||
it 'should set the count to 1 if it does not exist' do
|
||||
count_by_type = {}
|
||||
|
||||
expect(count_by_type.has_key?(type)).to be_falsey
|
||||
expect(
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:count_by_type => count_by_type
|
||||
)
|
||||
).to eq true
|
||||
expect(count_by_type[type]).to eq 1
|
||||
end
|
||||
|
||||
it 'should increment the count if it does exist' do
|
||||
original_count = 1
|
||||
count_by_type = {
|
||||
type => original_count
|
||||
}
|
||||
|
||||
expect(
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:count_by_type => count_by_type
|
||||
)
|
||||
).to eq true
|
||||
|
||||
incremented_count = original_count + 1
|
||||
expect(count_by_type[type]).to eq incremented_count
|
||||
end
|
||||
end
|
||||
end
|
||||
incremented_count = original_count + 1
|
||||
expect(count_by_type[type]).to eq incremented_count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -944,7 +782,7 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
module Msf
|
||||
module Modules
|
||||
module Mod617578696c696172792f72737065632f6d6f636b
|
||||
class Metasploit3
|
||||
class Metasploit
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1239,7 +1077,7 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
module Msf
|
||||
module Modules
|
||||
module Mod0
|
||||
class Metasploit3
|
||||
class Metasploit
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1339,50 +1177,5 @@ RSpec.describe Msf::Modules::Loader::Base do
|
|||
subject.send(:typed_path, type, module_reference_name)
|
||||
end
|
||||
end
|
||||
|
||||
context '#usable?' do
|
||||
context 'without metasploit_class responding to is_usable' do
|
||||
it 'should return true' do
|
||||
metasploit_class = double('Metasploit Class')
|
||||
expect(metasploit_class).not_to respond_to(:is_usable)
|
||||
|
||||
expect(subject.send(:usable?, metasploit_class)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with metasploit_class responding to is_usable' do
|
||||
it 'should delegate to metasploit_class.is_usable' do
|
||||
# not a proper return, but guarantees that delegation is actually happening
|
||||
usability = 'maybe'
|
||||
metasploit_class = double('Metasploit Class', :is_usable => usability)
|
||||
|
||||
expect(subject.send(:usable?, metasploit_class)).to eq usability
|
||||
end
|
||||
|
||||
context 'with error from metasploit_class.is_usable' do
|
||||
let(:error) do
|
||||
'Expected error'
|
||||
end
|
||||
|
||||
let(:metasploit_class) do
|
||||
metasploit_class = double('Metasploit Class')
|
||||
|
||||
expect(metasploit_class).to receive(:is_usable).and_raise(error)
|
||||
|
||||
metasploit_class
|
||||
end
|
||||
|
||||
it 'should log error' do
|
||||
expect(subject).to receive(:elog).with(/#{error}/)
|
||||
|
||||
subject.send(:usable?, metasploit_class)
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
expect(subject.send(:usable?, metasploit_class)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core/modules/metasploit_class_compatibility_error'
|
||||
|
||||
RSpec.describe Msf::Modules::MetasploitClassCompatibilityError do
|
||||
it_should_behave_like 'Msf::Modules::Error subclass #initialize'
|
||||
end
|
|
@ -1,268 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/modules/namespace'
|
||||
|
||||
RSpec.describe Msf::Modules::Namespace do
|
||||
let(:module_path) do
|
||||
"parent/path/type_directory/#{module_reference_name}.rb"
|
||||
end
|
||||
|
||||
let(:module_reference_name) do
|
||||
'module/reference/name'
|
||||
end
|
||||
|
||||
subject do
|
||||
mod = Module.new
|
||||
mod.extend described_class
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
context 'metasploit_class' do
|
||||
before(:example) do
|
||||
if major
|
||||
subject.const_set("Metasploit#{major}", Class.new)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without Metasploit<n> constant defined' do
|
||||
let(:major) do
|
||||
nil
|
||||
end
|
||||
|
||||
it 'should not be defined' do
|
||||
metasploit_constants = subject.constants.select { |constant|
|
||||
constant.to_s =~ /Metasploit/
|
||||
}
|
||||
|
||||
expect(metasploit_constants).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Metasploit1 constant defined' do
|
||||
let(:major) do
|
||||
1
|
||||
end
|
||||
|
||||
it 'should be defined' do
|
||||
expect(subject.const_defined?('Metasploit1')).to be_truthy
|
||||
end
|
||||
|
||||
it 'should return the class' do
|
||||
expect(subject.metasploit_class).to be_a Class
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Metasploit2 constant defined' do
|
||||
let(:major) do
|
||||
2
|
||||
end
|
||||
|
||||
it 'should be defined' do
|
||||
expect(subject.const_defined?('Metasploit2')).to be_truthy
|
||||
end
|
||||
|
||||
it 'should return the class' do
|
||||
expect(subject.metasploit_class).to be_a Class
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Metasploit3 constant defined' do
|
||||
let(:major) do
|
||||
3
|
||||
end
|
||||
|
||||
it 'should be defined' do
|
||||
expect(subject.const_defined?('Metasploit3')).to be_truthy
|
||||
end
|
||||
|
||||
it 'should return the class' do
|
||||
expect(subject.metasploit_class).to be_a Class
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Metasploit4 constant defined' do
|
||||
let(:major) do
|
||||
4
|
||||
end
|
||||
|
||||
it 'should be defined' do
|
||||
expect(subject.const_defined?('Metasploit4')).to be_truthy
|
||||
end
|
||||
|
||||
it 'should return the class' do
|
||||
expect(subject.metasploit_class).to be_a Class
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Metasploit5 constant defined' do
|
||||
let(:major) do
|
||||
5
|
||||
end
|
||||
|
||||
it 'should be defined' do
|
||||
expect(subject.const_defined?('Metasploit5')).to be_truthy
|
||||
end
|
||||
|
||||
it 'should be newer than Msf::Framework::Major' do
|
||||
expect(major).to be > Msf::Framework::Major
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
expect(subject.metasploit_class).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'metasploit_class!' do
|
||||
it 'should call metasploit_class' do
|
||||
expect(subject).to receive(:metasploit_class).and_return(Class.new)
|
||||
|
||||
subject.metasploit_class!(module_path, module_reference_name)
|
||||
end
|
||||
|
||||
context 'with metasploit_class' do
|
||||
let(:metasploit_class) do
|
||||
Class.new
|
||||
end
|
||||
|
||||
before(:example) do
|
||||
allow(subject).to receive(:metasploit_class).and_return(metasploit_class)
|
||||
end
|
||||
|
||||
it 'should return the metasploit_class' do
|
||||
expect(subject.metasploit_class!(module_path, module_reference_name)).to eq metasploit_class
|
||||
end
|
||||
end
|
||||
|
||||
context 'without metasploit_class' do
|
||||
before(:example) do
|
||||
allow(subject).to receive(:metasploit_class)
|
||||
end
|
||||
|
||||
it 'should raise a Msf::Modules::MetasploitClassCompatibilityError' do
|
||||
expect {
|
||||
subject.metasploit_class!(module_path, module_reference_name)
|
||||
}.to raise_error(Msf::Modules::MetasploitClassCompatibilityError)
|
||||
end
|
||||
|
||||
context 'the Msf::Modules::MetasploitClassCompatibilityError' do
|
||||
it 'should include the module path' do
|
||||
error = nil
|
||||
|
||||
begin
|
||||
subject.metasploit_class!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::MetasploitClassCompatibilityError => error
|
||||
end
|
||||
|
||||
expect(error).not_to be_nil
|
||||
expect(error.to_s).to include(module_path)
|
||||
end
|
||||
|
||||
it 'should include the module reference name' do
|
||||
error = nil
|
||||
|
||||
begin
|
||||
subject.metasploit_class!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::MetasploitClassCompatibilityError => error
|
||||
end
|
||||
|
||||
expect(error).not_to be_nil
|
||||
expect(error.to_s).to include(module_reference_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'version_compatible!' do
|
||||
context 'without RequiredVersions' do
|
||||
it 'should not be defined' do
|
||||
expect(subject.const_defined?('RequiredVersions')).to be_falsey
|
||||
end
|
||||
|
||||
it 'should not raise an error' do
|
||||
expect {
|
||||
subject.version_compatible!(module_path, module_reference_name)
|
||||
}.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'with RequiredVersions defined' do
|
||||
let(:minimum_api_version) do
|
||||
1
|
||||
end
|
||||
|
||||
let(:minimum_core_version) do
|
||||
1
|
||||
end
|
||||
|
||||
before(:example) do
|
||||
subject.const_set(
|
||||
:RequiredVersions,
|
||||
[
|
||||
minimum_core_version,
|
||||
minimum_api_version
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
context 'with minimum Core version' do
|
||||
it 'is <= Metasploit::Framework::Core::GEM_VERSION when converted to Gem::Version' do
|
||||
expect(Gem::Version.new(minimum_core_version.to_s)).to be <= Metasploit::Framework::Core::GEM_VERSION
|
||||
end
|
||||
|
||||
context 'without minimum API version' do
|
||||
let(:minimum_api_version) do
|
||||
2
|
||||
end
|
||||
|
||||
it 'is > Metasploit::Framework::API::GEM_VERSION when converted to Gem::Version' do
|
||||
expect(Gem::Version.new(minimum_api_version.to_s)).to be > Metasploit::Framework::API::GEM_VERSION
|
||||
end
|
||||
|
||||
it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
|
||||
end
|
||||
|
||||
context 'with minimum API version' do
|
||||
it 'should not raise an error' do
|
||||
expect {
|
||||
subject.version_compatible!(module_path, module_reference_name)
|
||||
}.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without minimum Core version' do
|
||||
let(:minimum_core_version) do
|
||||
5
|
||||
end
|
||||
|
||||
it 'is > Metasploit::Framework::Core::GEM_VERSION when converted to Gem::Version' do
|
||||
expect(Gem::Version.new(minimum_core_version.to_s)).to be > Metasploit::Framework::Core::GEM_VERSION
|
||||
end
|
||||
|
||||
context 'without minimum API version' do
|
||||
let(:minimum_api_version) do
|
||||
2
|
||||
end
|
||||
|
||||
it 'is > Metasploit::Framework::API::GEM_VERSION when converted to Gem::Version' do
|
||||
expect(Gem::Version.new(minimum_api_version.to_s)).to be > Metasploit::Framework::API::GEM_VERSION
|
||||
end
|
||||
|
||||
it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
|
||||
end
|
||||
|
||||
context 'with minimum API version' do
|
||||
it 'is <= Metasploit::Framework::API::GEM_VERSION when converted to Gem::Version' do
|
||||
expect(Gem::Version.new(minimum_api_version.to_s)).to be <= Metasploit::Framework::API::GEM_VERSION
|
||||
end
|
||||
|
||||
it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,63 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Msf::Modules::VersionCompatibilityError do
|
||||
it_should_behave_like 'Msf::Modules::Error subclass #initialize' do
|
||||
let(:minimum_api_version) do
|
||||
1
|
||||
end
|
||||
|
||||
let(:minimum_core_version) do
|
||||
2
|
||||
end
|
||||
|
||||
it 'should say cause was version check' do
|
||||
expect(subject.to_s).to match(/due to version check/)
|
||||
end
|
||||
|
||||
context 'with :minimum_api_version' do
|
||||
subject do
|
||||
described_class.new(
|
||||
:minimum_api_version => minimum_api_version
|
||||
)
|
||||
end
|
||||
|
||||
it 'should set minimum_api_version' do
|
||||
expect(subject.minimum_api_version).to eq minimum_api_version
|
||||
end
|
||||
|
||||
it 'should include minimum_api_version in error' do
|
||||
expect(subject.to_s).to match(/due to version check \(requires API >= #{minimum_api_version}\)/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :minimum_api_version and :minimum_core_version' do
|
||||
subject do
|
||||
described_class.new(
|
||||
:minimum_api_version => minimum_api_version,
|
||||
:minimum_core_version => minimum_core_version
|
||||
)
|
||||
end
|
||||
|
||||
it 'should include minimum_api_version and minimum_core_version in error' do
|
||||
expect(subject.to_s).to match(/due to version check \(requires API >= #{minimum_api_version} and Core >= #{minimum_core_version}\)/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :minimum_core_version' do
|
||||
subject do
|
||||
described_class.new(
|
||||
:minimum_core_version => minimum_core_version
|
||||
)
|
||||
end
|
||||
|
||||
it 'should set minimum_core_version' do
|
||||
expect(subject.minimum_core_version).to eq minimum_core_version
|
||||
end
|
||||
|
||||
it 'should include minimum_core_version in error' do
|
||||
expect(subject.to_s).to match(/due to version check \(requires Core >= #{minimum_core_version}\)/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue