wpscan 3.0.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +1 -1
  4. data/app/controllers.rb +1 -0
  5. data/app/controllers/aliases.rb +12 -0
  6. data/app/controllers/core.rb +3 -5
  7. data/app/controllers/enumeration.rb +2 -28
  8. data/app/controllers/enumeration/enum_methods.rb +12 -2
  9. data/app/controllers/wp_version.rb +4 -0
  10. data/app/finders/main_theme/css_style.rb +2 -2
  11. data/app/finders/main_theme/urls_in_homepage.rb +3 -3
  12. data/app/finders/plugin_version.rb +1 -8
  13. data/app/finders/plugins.rb +13 -4
  14. data/app/finders/plugins/body_pattern.rb +27 -0
  15. data/app/finders/plugins/comment.rb +31 -0
  16. data/app/finders/plugins/config_parser.rb +31 -0
  17. data/app/finders/plugins/header_pattern.rb +41 -0
  18. data/app/finders/plugins/javascript_var.rb +29 -0
  19. data/app/finders/plugins/known_locations.rb +5 -5
  20. data/app/finders/plugins/query_parameter.rb +25 -0
  21. data/app/finders/plugins/urls_in_homepage.rb +4 -8
  22. data/app/finders/plugins/xpath.rb +29 -0
  23. data/app/finders/theme_version.rb +1 -1
  24. data/app/finders/theme_version/woo_framework_meta_generator.rb +2 -2
  25. data/app/finders/themes/known_locations.rb +5 -5
  26. data/app/finders/themes/urls_in_homepage.rb +2 -2
  27. data/app/finders/users/login_error_messages.rb +1 -4
  28. data/app/finders/users/wp_json_api.rb +2 -2
  29. data/app/finders/wp_items/urls_in_homepage.rb +1 -1
  30. data/app/finders/wp_version.rb +21 -18
  31. data/app/models/plugin.rb +4 -4
  32. data/app/models/theme.rb +6 -6
  33. data/app/models/timthumb.rb +1 -3
  34. data/app/models/wp_item.rb +15 -15
  35. data/app/views/json/enumeration/plugins.erb +1 -1
  36. data/app/views/json/enumeration/themes.erb +1 -1
  37. data/app/views/json/wp_item.erb +1 -1
  38. data/bin/wpscan +2 -1
  39. data/lib/wpscan/db.rb +14 -10
  40. data/lib/wpscan/db/dynamic_finders/base.rb +41 -0
  41. data/lib/wpscan/db/dynamic_finders/plugin.rb +111 -0
  42. data/lib/wpscan/db/dynamic_finders/theme.rb +16 -0
  43. data/lib/wpscan/db/dynamic_finders/wordpress.rb +75 -0
  44. data/lib/wpscan/db/updater.rb +2 -2
  45. data/lib/wpscan/finders.rb +13 -1
  46. data/lib/wpscan/finders/dynamic_finder/finder.rb +66 -0
  47. data/lib/wpscan/finders/dynamic_finder/version/body_pattern.rb +28 -0
  48. data/lib/wpscan/finders/dynamic_finder/version/comment.rb +16 -0
  49. data/lib/wpscan/finders/dynamic_finder/version/config_parser.rb +52 -0
  50. data/lib/wpscan/finders/dynamic_finder/version/finder.rb +29 -0
  51. data/lib/wpscan/finders/dynamic_finder/version/header_pattern.rb +28 -0
  52. data/lib/wpscan/finders/dynamic_finder/version/javascript_var.rb +56 -0
  53. data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +62 -0
  54. data/lib/wpscan/finders/dynamic_finder/version/xpath.rb +34 -0
  55. data/lib/wpscan/finders/dynamic_finder/wp_item_version.rb +42 -0
  56. data/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +96 -0
  57. data/lib/wpscan/finders/dynamic_finder/wp_version.rb +60 -0
  58. data/lib/wpscan/helper.rb +11 -0
  59. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +16 -1
  60. data/lib/wpscan/version.rb +1 -1
  61. metadata +32 -24
  62. data/app/finders/plugin_version/layer_slider/translation_file.rb +0 -40
  63. data/app/finders/plugin_version/revslider/release_log.rb +0 -35
  64. data/app/finders/plugin_version/shareaholic/meta_tag.rb +0 -27
  65. data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +0 -27
  66. data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +0 -31
  67. data/app/finders/plugin_version/w3_total_cache/headers.rb +0 -28
  68. data/app/finders/plugins/comments.rb +0 -31
  69. data/app/finders/plugins/headers.rb +0 -36
  70. data/app/finders/wp_version/homepage_stylesheet_numbers.rb +0 -59
  71. data/app/finders/wp_version/install_stylesheet_numbers.rb +0 -16
  72. data/app/finders/wp_version/meta_generator.rb +0 -27
  73. data/app/finders/wp_version/opml_generator.rb +0 -23
  74. data/app/finders/wp_version/sitemap_generator.rb +0 -23
  75. data/app/finders/wp_version/upgrade_stylesheet_numbers.rb +0 -13
  76. data/lib/wpscan/db/dynamic_finders.rb +0 -55
  77. data/lib/wpscan/finders/finder/plugin_version/comments.rb +0 -27
@@ -2,6 +2,8 @@ module WPScan
2
2
  module Finders
3
3
  module Plugins
4
4
  # URLs In Homepage Finder
5
+ # Typically, the items detected from URLs like
6
+ # /wp-content/plugins/<slug>/
5
7
  class UrlsInHomepage < CMSScanner::Finders::Finder
6
8
  include WpItems::URLsInHomepage
7
9
 
@@ -11,14 +13,8 @@ module WPScan
11
13
  def passive(opts = {})
12
14
  found = []
13
15
 
14
- (items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |name|
15
- found << Plugin.new(name, target, opts.merge(found_by: found_by, confidence: 80))
16
- end
17
-
18
- DB::DynamicPluginFinders.urls_in_page.each do |name, config|
19
- next unless target.homepage_res.html.xpath(config['xpath']).any?
20
-
21
- found << Plugin.new(name, target, opts.merge(found_by: found_by, confidence: 100))
16
+ (items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
17
+ found << Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
22
18
  end
23
19
 
24
20
  found
@@ -0,0 +1,29 @@
1
+ module WPScan
2
+ module Finders
3
+ module Plugins
4
+ # Plugins finder from the Dynamic Finder 'Xpath'
5
+ class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
6
+ DEFAULT_CONFIDENCE = 40
7
+
8
+ # @param [ Hash ] opts The options from the #passive, #aggressive methods
9
+ # @param [ Typhoeus::Response ] response
10
+ # @param [ String ] slug
11
+ # @param [ String ] klass
12
+ # @param [ Hash ] config The related dynamic finder config hash
13
+ #
14
+ # @return [ Plugin ] The detected plugin in the response, related to the config
15
+ def process_response(opts, response, slug, klass, config)
16
+ response.html.xpath(config['xpath']).each do |node|
17
+ next if config['pattern'] && !node.text.match(config['pattern'])
18
+
19
+ return Plugin.new(
20
+ slug,
21
+ target,
22
+ opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -21,7 +21,7 @@ module WPScan
21
21
  #
22
22
  # @param [ WPScan::Theme ] theme
23
23
  def load_specific_finders(theme)
24
- module_name = theme.classify_name.to_sym
24
+ module_name = theme.classify
25
25
 
26
26
  return unless Finders::ThemeVersion.constants.include?(module_name)
27
27
 
@@ -7,9 +7,9 @@ module WPScan
7
7
  #
8
8
  # @return [ Version ]
9
9
  def passive(_opts = {})
10
- return unless target.target.homepage_res.body =~ Finders::MainTheme::WooFrameworkMetaGenerator::PATTERN
10
+ return unless target.blog.homepage_res.body =~ Finders::MainTheme::WooFrameworkMetaGenerator::PATTERN
11
11
 
12
- return unless Regexp.last_match[1] == target.name
12
+ return unless Regexp.last_match[1] == target.slug
13
13
 
14
14
  WPScan::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
15
15
  end
@@ -12,12 +12,12 @@ module WPScan
12
12
  def aggressive(opts = {})
13
13
  found = []
14
14
 
15
- enumerate(target_urls(opts), opts) do |res, name|
15
+ enumerate(target_urls(opts), opts) do |res, slug|
16
16
  # TODO: follow the location (from enumerate()) and remove the 301 here ?
17
17
  # As a result, it might remove false positive due to redirection to the homepage
18
18
  next unless [200, 401, 403, 301].include?(res.code)
19
19
 
20
- found << WPScan::Theme.new(name, target, opts.merge(found_by: found_by, confidence: 80))
20
+ found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
21
21
  end
22
22
 
23
23
  found
@@ -28,12 +28,12 @@ module WPScan
28
28
  #
29
29
  # @return [ Hash ]
30
30
  def target_urls(opts = {})
31
- names = opts[:list] || DB::Themes.vulnerable_slugs
31
+ slugs = opts[:list] || DB::Themes.vulnerable_slugs
32
32
  urls = {}
33
33
  themes_url = target.url('wp-content/themes/')
34
34
 
35
- names.each do |name|
36
- urls["#{themes_url}#{URI.encode(name)}/"] = name
35
+ slugs.each do |slug|
36
+ urls["#{themes_url}#{URI.encode(slug)}/"] = slug
37
37
  end
38
38
 
39
39
  urls
@@ -11,8 +11,8 @@ module WPScan
11
11
  def passive(opts = {})
12
12
  found = []
13
13
 
14
- (items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |name|
15
- found << WPScan::Theme.new(name, target, opts.merge(found_by: found_by, confidence: 80))
14
+ (items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
15
+ found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
16
16
  end
17
17
 
18
18
  found
@@ -17,10 +17,7 @@ module WPScan
17
17
  found = []
18
18
 
19
19
  usernames(opts).each do |username|
20
- res = target.do_login(username, SecureRandom.hex[0, 8])
21
-
22
- return found unless res.code == 200
23
-
20
+ res = target.do_login(username, SecureRandom.hex[0, 8])
24
21
  error = res.html.css('div#login_error').text.strip
25
22
 
26
23
  return found if error.empty? # Protection plugin / error disabled
@@ -12,7 +12,7 @@ module WPScan
12
12
  def aggressive(_opts = {})
13
13
  found = []
14
14
 
15
- JSON.parse(Browser.get(api_url).body).each do |user|
15
+ JSON.parse(Browser.get(api_url).body)&.each do |user|
16
16
  found << WPScan::User.new(user['slug'],
17
17
  id: user['id'],
18
18
  found_by: found_by,
@@ -21,7 +21,7 @@ module WPScan
21
21
  end
22
22
 
23
23
  found
24
- rescue JSON::ParserError
24
+ rescue JSON::ParserError, TypeError
25
25
  found
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ module WPScan
30
30
  code = tag.text.to_s
31
31
  next if code.empty?
32
32
 
33
- code.scan(item_code_pattern(type)).flatten.uniq.each { |name| found << name }
33
+ code.scan(item_code_pattern(type)).flatten.uniq.each { |slug| found << slug }
34
34
  end
35
35
 
36
36
  uniq ? found.uniq.sort : found.sort
@@ -1,17 +1,23 @@
1
- require_relative 'wp_version/meta_generator'
2
1
  require_relative 'wp_version/rss_generator'
3
2
  require_relative 'wp_version/atom_generator'
4
3
  require_relative 'wp_version/rdf_generator'
5
4
  require_relative 'wp_version/readme'
6
- require_relative 'wp_version/sitemap_generator'
7
- require_relative 'wp_version/opml_generator'
8
- require_relative 'wp_version/homepage_stylesheet_numbers'
9
- require_relative 'wp_version/install_stylesheet_numbers'
10
- require_relative 'wp_version/upgrade_stylesheet_numbers'
11
5
  require_relative 'wp_version/unique_fingerprinting'
12
6
 
13
7
  module WPScan
14
8
  module Finders
9
+ # Specific Finders container to filter the version detected
10
+ # and remove the one with low confidence to avoid false
11
+ # positive when there is not enought information to accurately
12
+ # determine it.
13
+ class WpVersionFinders < UniqueFinders
14
+ def filter_findings
15
+ best_finding = super
16
+
17
+ best_finding && best_finding.confidence >= 40 ? best_finding : false
18
+ end
19
+ end
20
+
15
21
  module WpVersion
16
22
  # Wp Version Finder
17
23
  class Base
@@ -19,18 +25,15 @@ module WPScan
19
25
 
20
26
  # @param [ WPScan::Target ] target
21
27
  def initialize(target)
22
- finders <<
23
- WpVersion::MetaGenerator.new(target) <<
24
- WpVersion::RSSGenerator.new(target) <<
25
- WpVersion::AtomGenerator.new(target) <<
26
- WpVersion::HomepageStylesheetNumbers.new(target) <<
27
- WpVersion::InstallStylesheetNumbers.new(target) <<
28
- WpVersion::UpgradeStylesheetNumbers.new(target) <<
29
- WpVersion::RDFGenerator.new(target) <<
30
- WpVersion::Readme.new(target) <<
31
- WpVersion::SitemapGenerator.new(target) <<
32
- WpVersion::OpmlGenerator.new(target) <<
33
- WpVersion::UniqueFingerprinting.new(target)
28
+ (WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
29
+ %w[RSSGenerator AtomGenerator RDFGenerator Readme UniqueFingerprinting]
30
+ ).each do |finder_name|
31
+ finders << WpVersion.const_get(finder_name.to_sym).new(target)
32
+ end
33
+ end
34
+
35
+ def finders
36
+ @finders ||= Finders::WpVersionFinders.new
34
37
  end
35
38
  end
36
39
  end
data/app/models/plugin.rb CHANGED
@@ -2,15 +2,15 @@ module WPScan
2
2
  # WordPress Plugin
3
3
  class Plugin < WpItem
4
4
  # See WpItem
5
- def initialize(name, target, opts = {})
6
- super(name, target, opts)
5
+ def initialize(slug, blog, opts = {})
6
+ super(slug, blog, opts)
7
7
 
8
- @uri = Addressable::URI.parse(target.url("wp-content/plugins/#{name}/"))
8
+ @uri = Addressable::URI.parse(blog.url("wp-content/plugins/#{slug}/"))
9
9
  end
10
10
 
11
11
  # @return [ JSON ]
12
12
  def db_data
13
- DB::Plugin.db_data(name)
13
+ DB::Plugin.db_data(slug)
14
14
  end
15
15
 
16
16
  # @param [ Hash ] opts
data/app/models/theme.rb CHANGED
@@ -5,10 +5,10 @@ module WPScan
5
5
  :license, :license_uri, :tags, :text_domain
6
6
 
7
7
  # See WpItem
8
- def initialize(name, target, opts = {})
9
- super(name, target, opts)
8
+ def initialize(slug, blog, opts = {})
9
+ super(slug, blog, opts)
10
10
 
11
- @uri = Addressable::URI.parse(target.url("wp-content/themes/#{name}/"))
11
+ @uri = Addressable::URI.parse(blog.url("wp-content/themes/#{slug}/"))
12
12
  @style_url = opts[:style_url] || url('style.css')
13
13
 
14
14
  parse_style
@@ -16,7 +16,7 @@ module WPScan
16
16
 
17
17
  # @return [ JSON ]
18
18
  def db_data
19
- DB::Theme.db_data(name)
19
+ DB::Theme.db_data(slug)
20
20
  end
21
21
 
22
22
  # @param [ Hash ] opts
@@ -39,7 +39,7 @@ module WPScan
39
39
  confidence: 100
40
40
  ).merge(version_detection: version_detection_opts)
41
41
 
42
- self.class.new(template, target, opts)
42
+ self.class.new(template, blog, opts)
43
43
  end
44
44
 
45
45
  # @param [ Integer ] depth
@@ -89,7 +89,7 @@ module WPScan
89
89
  def parse_style_tag(body, tag)
90
90
  value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
91
91
 
92
- value && !value.strip.empty? ? value.strip : nil # rubocop:disable Style/SafeNavigation
92
+ value && !value.strip.empty? ? value.strip : nil
93
93
  end
94
94
 
95
95
  def ==(other)
@@ -18,9 +18,7 @@ module WPScan
18
18
  #
19
19
  # @return [ WPScan::Version, false ]
20
20
  def version(opts = {})
21
- if @version.nil?
22
- @version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts))
23
- end
21
+ @version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
24
22
 
25
23
  @version
26
24
  end
@@ -9,20 +9,22 @@ module WPScan
9
9
  READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
10
10
  CHANGELOGS = %w[changelog.txt CHANGELOG.md changelog.md].freeze
11
11
 
12
- attr_reader :uri, :name, :detection_opts, :version_detection_opts, :target, :db_data
12
+ attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :db_data
13
13
 
14
- # @param [ String ] name The plugin/theme name
15
- # @param [ Target ] target The targeted blog
14
+ delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, to: :blog
15
+
16
+ # @param [ String ] slug The plugin/theme slug
17
+ # @param [ Target ] blog The targeted blog
16
18
  # @param [ Hash ] opts
17
19
  # @option opts [ Symbol ] :mode The detection mode to use
18
20
  # @option opts [ Hash ] :version_detection The options to use when looking for the version
19
21
  # @option opts [ String ] :url The URL of the item
20
- def initialize(name, target, opts = {})
21
- @name = URI.decode(name)
22
- @target = target
23
- @uri = Addressable::URI.parse(opts[:url]) if opts[:url]
22
+ def initialize(slug, blog, opts = {})
23
+ @slug = URI.decode(slug)
24
+ @blog = blog
25
+ @uri = Addressable::URI.parse(opts[:url]) if opts[:url]
24
26
 
25
- @detection_opts = { mode: opts[:mode] }
27
+ @detection_opts = { mode: opts[:mode] }
26
28
  @version_detection_opts = opts[:version_detection] || {}
27
29
 
28
30
  parse_finding_options(opts)
@@ -95,18 +97,16 @@ module WPScan
95
97
 
96
98
  # @return [ Boolean ]
97
99
  def ==(other)
98
- return false unless self.class == other.class
99
-
100
- name == other.name
100
+ self.class == other.class && slug == other.slug
101
101
  end
102
102
 
103
103
  def to_s
104
- name
104
+ slug
105
105
  end
106
106
 
107
- # @return [ Symbol ] The Class name associated to the item name
108
- def classify_name
109
- name.to_s.tr('-', '_').camelize.to_s.to_sym
107
+ # @return [ Symbol ] The Class symbol associated to the item
108
+ def classify
109
+ @classify ||= classify_slug(slug)
110
110
  end
111
111
 
112
112
  # @return [ String ] The readme url if found
@@ -2,7 +2,7 @@
2
2
  <% unless @plugins.empty? -%>
3
3
  <% last_index = @plugins.size - 1 -%>
4
4
  <% @plugins.each_with_index do |plugin, index| -%>
5
- <%= plugin.name.to_json %>: {
5
+ <%= plugin.slug.to_json %>: {
6
6
  <%= render('@wp_item', wp_item: plugin) %>,
7
7
  <%= render('@finding', item: plugin) -%>,
8
8
  <% if plugin.version -%>
@@ -2,7 +2,7 @@
2
2
  <% unless @themes.empty? -%>
3
3
  <% last_index = @themes.size - 1 -%>
4
4
  <% @themes.each_with_index do |theme, index| -%>
5
- <%= theme.name.to_json %>: {
5
+ <%= theme.slug.to_json %>: {
6
6
  <%= render('@theme', theme: theme) -%>
7
7
  }<% unless index == last_index -%>,<% end -%>
8
8
  <% end -%>
@@ -1,4 +1,4 @@
1
- "name": <%= @wp_item.name.to_json %>,
1
+ "slug": <%= @wp_item.slug.to_json %>,
2
2
  "location": <%= @wp_item.url.to_json %>,
3
3
  "latest_version": <%= @wp_item.latest_version ? @wp_item.latest_version.number.to_json : nil.to_json %>,
4
4
  "last_updated": <%= @wp_item.last_updated.to_json %>,
data/bin/wpscan CHANGED
@@ -9,7 +9,8 @@ WPScan::Scan.new do |s|
9
9
  WPScan::Controller::WpVersion.new <<
10
10
  WPScan::Controller::MainTheme.new <<
11
11
  WPScan::Controller::Enumeration.new <<
12
- WPScan::Controller::BruteForce.new
12
+ WPScan::Controller::BruteForce.new <<
13
+ WPScan::Controller::Aliases.new
13
14
 
14
15
  s.run
15
16
  end
data/lib/wpscan/db.rb CHANGED
@@ -1,10 +1,14 @@
1
- require 'wpscan/db/wp_item'
2
- require 'wpscan/db/updater'
3
- require 'wpscan/db/wp_items'
4
- require 'wpscan/db/plugins'
5
- require 'wpscan/db/themes'
6
- require 'wpscan/db/plugin'
7
- require 'wpscan/db/theme'
8
- require 'wpscan/db/wp_version'
9
- require 'wpscan/db/fingerprints'
10
- require 'wpscan/db/dynamic_finders'
1
+ require_relative 'db/wp_item'
2
+ require_relative 'db/updater'
3
+ require_relative 'db/wp_items'
4
+ require_relative 'db/plugins'
5
+ require_relative 'db/themes'
6
+ require_relative 'db/plugin'
7
+ require_relative 'db/theme'
8
+ require_relative 'db/wp_version'
9
+ require_relative 'db/fingerprints'
10
+
11
+ require_relative 'db/dynamic_finders/base'
12
+ require_relative 'db/dynamic_finders/plugin'
13
+ require_relative 'db/dynamic_finders/theme'
14
+ require_relative 'db/dynamic_finders/wordpress'
@@ -0,0 +1,41 @@
1
+ module WPScan
2
+ module DB
3
+ module DynamicFinders
4
+ class Base
5
+ # @return [ String ]
6
+ def self.db_file
7
+ @db_file ||= File.join(DB_DIR, 'dynamic_finders.yml')
8
+ end
9
+
10
+ # @return [ Hash ]
11
+ def self.db_data
12
+ # true allows aliases to be loaded
13
+ @db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
14
+ end
15
+
16
+ # @return [ Array<Symbol> ]
17
+ def self.allowed_classes
18
+ @allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
19
+ end
20
+
21
+ # @param [ Symbol ] sym
22
+ def self.method_missing(sym)
23
+ super unless sym =~ /\A(passive|aggressive)_(.*)_finder_configs\z/i
24
+
25
+ finder_class = Regexp.last_match[2].camelize.to_sym
26
+
27
+ raise "#{finder_class} is not allowed as a Dynamic Finder" unless allowed_classes.include?(finder_class)
28
+
29
+ finder_configs(
30
+ finder_class,
31
+ Regexp.last_match[1] == 'aggressive'
32
+ )
33
+ end
34
+
35
+ def self.respond_to_missing?(sym, *_args)
36
+ sym =~ /\A(passive|aggressive)_(.*)_finder_configs\z/i
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end