metasploit-model 0.25.7 → 0.26.1

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 (26) hide show
  1. checksums.yaml +8 -8
  2. data/app/models/metasploit/model/search/operation/association.rb +57 -0
  3. data/app/models/metasploit/model/search/operator/association.rb +24 -14
  4. data/app/models/metasploit/model/search/operator/base.rb +19 -3
  5. data/app/models/metasploit/model/search/operator/single.rb +21 -1
  6. data/app/models/metasploit/model/search/query.rb +20 -1
  7. data/lib/metasploit/model/association/tree.rb +130 -0
  8. data/lib/metasploit/model/search.rb +61 -27
  9. data/lib/metasploit/model/search/association.rb +152 -8
  10. data/lib/metasploit/model/search/attribute.rb +112 -22
  11. data/lib/metasploit/model/search/operator.rb +53 -1
  12. data/lib/metasploit/model/search/operator/help.rb +39 -1
  13. data/lib/metasploit/model/search/with.rb +44 -1
  14. data/lib/metasploit/model/version.rb +2 -2
  15. data/spec/app/models/metasploit/model/search/operation/association_spec.rb +67 -0
  16. data/spec/app/models/metasploit/model/search/operator/association_spec.rb +76 -76
  17. data/spec/app/models/metasploit/model/search/operator/deprecated/author_spec.rb +54 -18
  18. data/spec/app/models/metasploit/model/search/operator/deprecated/authority_spec.rb +20 -8
  19. data/spec/app/models/metasploit/model/search/operator/deprecated/platform_spec.rb +20 -8
  20. data/spec/app/models/metasploit/model/search/operator/deprecated/ref_spec.rb +86 -26
  21. data/spec/app/models/metasploit/model/search/operator/deprecated/text_spec.rb +63 -21
  22. data/spec/lib/metasploit/model/search/association/tree_spec.rb +385 -0
  23. data/spec/lib/metasploit/model/search/association_spec.rb +99 -10
  24. data/spec/lib/metasploit/model/search_spec.rb +48 -107
  25. data/spec/support/shared/examples/search/query/metasploit/model/search/operator/deprecated/authority.rb +19 -7
  26. metadata +7 -1
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MzQ5ZTQ5ZTE0OWUyM2ZjMmYyNTNkM2NjNDgxZDE2YjkwMjg2OWYxMw==
4
+ YzE5ZmE5MjgxN2RlZjhlMDk1NTM1OTcwYjZiZWRjMmRiOWI5ZDQ1Ng==
5
5
  data.tar.gz: !binary |-
6
- OWEyNjhlMmQwYzkwMzAyY2U1M2Q5MmNlNTAzOGFmYjQzYmE0YjlmYQ==
6
+ NmJjYTg0NWIwZGIxNzUyZGMwYmUyOTVlMzdlMGNmNGU5MDNkNjJiMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YzE0OTc5MGM1MDM0MmI5MTBmM2U3Y2U2NjZkY2Q5Y2FkNjM2ZGM1YWViZTgx
10
- ZjllODJiMGIwOTQyNGNiNTEzNTZiMzRiNjk3NWE1ZjkxOGE4NTU4NDk3NjA1
11
- NDhiNjI2NTMyYzBjZWE1NTJkMTZhM2IyNDRhZDZlNTQ4MGE1MmI=
9
+ MTNhYzY1MWNiNTNkNWY0ODBkZGMzNDMxN2JiYjA4YTlkNzg4Mzg3ZTRiNWI2
10
+ YTVmOGY1YWI1ZTVkZDdkMDJkN2M1NzU0ZGUzMmVkNDBiMThlZGVhMTg3MmRh
11
+ ZmQwZDI0NGJlYWRhMTQ3YTg5MDFiNmVlNDNiYjZjMzAwZWI4MjM=
12
12
  data.tar.gz: !binary |-
13
- NGZmNDQxZTczOWJiMTA4ZmU2ODgwOWEwNTViNjBiY2YyNjMyNGZkYTRmNjIy
14
- ODA2NDRlOThlMzNiODU4NGU4Y2IzMzlkZmVmNDE4N2E0YTM1ZTc2NTk0NjM5
15
- YTA4NTY2YTU2ZWZhYmIwNmFlNWYyNmRiMDk2NzBlM2Q2YzhhMTM=
13
+ OWJmNTc3ZGM2MmJmZjUwNjJlZTUzZTI0NTY1ZjRjZWVhNWNkYWFiODQ4YjAz
14
+ OGJlZjM4ZDI5NjJkODI5MjdlOTEzZDVjYTI2YmRkMGJmMjNjMDUzOTM3NTVh
15
+ YTUyODRhMDViOWJiODg0Zjc5ZDI0NmQxYTg3M2VlNmEwNzdhNzA=
@@ -0,0 +1,57 @@
1
+ # An operation with a {Metasploit::Model::Search::Operator::Association} for
2
+ # {Metasploit::Model::Search::Operation::Base#operator} that wraps a {#source_operation} produced by the
3
+ # {Metasploit::Model::Search::Operator::Association#source_operator}. This allows an arbitrary number of associations
4
+ # to be changed together until a non-association operation is found that actually validates the value.
5
+ class Metasploit::Model::Search::Operation::Association < Metasploit::Model::Search::Operation::Base
6
+ #
7
+ # Attributes
8
+ #
9
+
10
+ # @!attribute source_operation
11
+ # The operation from the {Metasploit::Model::Search::Operator::Association#source_operator}.
12
+ #
13
+ # @return [Metasploit::Model::Search::Operation::Base]
14
+ attr_accessor :source_operation
15
+
16
+ #
17
+ #
18
+ # Validations
19
+ #
20
+ #
21
+
22
+ #
23
+ # Validation Methods
24
+ #
25
+
26
+ validate :source_operation_valid
27
+
28
+ #
29
+ # Attribute Validations
30
+ #
31
+
32
+ validates :source_operation,
33
+ presence: true
34
+
35
+ #
36
+ # Instance Methods
37
+ #
38
+
39
+ # Explicitly remove value attribute so code that depends on the old behavior will break so downstream developers know
40
+ # to update their code to use source_operation.
41
+ undef_method :value
42
+ undef_method :value=
43
+
44
+ private
45
+
46
+ # Validates that {#source_operation} is valid.
47
+ #
48
+ # @return [void]
49
+ def source_operation_valid
50
+ # presence validation handles errors when nil
51
+ if source_operation
52
+ unless source_operation.valid?
53
+ errors.add(:source_operation, :invalid)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,43 +1,53 @@
1
1
  # A search operator declared with
2
2
  # {Metasploit::Model::Search::Association::ClassMethods#search_association search_association}.
3
- class Metasploit::Model::Search::Operator::Association < Metasploit::Model::Search::Operator::Single
3
+ class Metasploit::Model::Search::Operator::Association < Metasploit::Model::Search::Operator::Base
4
4
  #
5
5
  # Attributes
6
6
  #
7
7
 
8
8
  # @!attribute [rw] association
9
- # The association on which {Metasploit::Model::Search::Operator::Attribute#attribute} is declared searchable.
9
+ # The association on which {#source_operator} was declared.
10
10
  #
11
11
  # @return [Symbol] association on {Metasploit::Model::Search::Operator::Base#klass klass}.
12
12
  attr_accessor :association
13
13
 
14
- # @!attribute [rw] attribute_operator
15
- # The {Metasploit::Model::Search::Operator::Attribute operator} as declared on the {#association} class.
14
+ # @!attribute [rw] source_operator
15
+ # The {Metasploit::Model::Search::Operator::Base operator} as declared on the {#association} class.
16
16
  #
17
- # @return [Metasploit::Model::Search::Operator::Attribute]
18
- attr_accessor :attribute_operator
17
+ # @return [Metasploit::Model::Search::Operator::Base]
18
+ attr_accessor :source_operator
19
19
 
20
20
  #
21
21
  # Validations
22
22
  #
23
23
 
24
24
  validates :association, :presence => true
25
- validates :attribute_operator, :presence => true
25
+ validates :source_operator, :presence => true
26
26
 
27
27
  #
28
28
  # Methods
29
29
  #
30
30
 
31
- delegate :attribute,
32
- :attribute_set,
33
- :help,
34
- :type,
35
- :to => :attribute_operator
31
+ delegate :help,
32
+ to: :source_operator
36
33
 
37
34
  # The name of this operator.
38
35
  #
39
- # @return [String] <association>.<attribute>
36
+ # @return [String] <association>.<source_operator.name>
40
37
  def name
41
- @name ||= "#{association}.#{attribute}".to_sym
38
+ @name ||= "#{association}.#{source_operator.name}".to_sym
39
+ end
40
+
41
+ # Creates a {Metasploit::Model::Search::Operation::Association} to wrap the original operation returned by
42
+ # {#source_operator}'s `#operate_on`.
43
+ #
44
+ # @param formatted_value [#to_s] Formatted value to pass to {#source_operator}.
45
+ # @return [Metasploit::Model::Search::Operation::Association] Association operation with the original operation from
46
+ # {#source_operator} operating on `formatted_value`.
47
+ def operate_on(formatted_value)
48
+ Metasploit::Model::Search::Operation::Association.new(
49
+ operator: self,
50
+ source_operation: source_operator.operate_on(formatted_value)
51
+ )
42
52
  end
43
53
  end
@@ -1,7 +1,23 @@
1
- # @abstract Declare search operators using {Metasploit::Model::Search::ClassMethods#search_attribute} and include
2
- # operators from associations with {Metasploit::Model::Search::ClassMethods#search_association}.
1
+ # Instead of writing an operator completely from scratch, you can subclass
2
+ # {Metasploit::Model::Search::Operator::Base}.
3
+ #
4
+ # class MyOperator < Metasploit::Model::Search::Operator::Base
5
+ # # Name of this operator. The name of the operator is matched to the string before the ':' in a formatted
6
+ # # operation.
7
+ # #
8
+ # # @return [Symbol]
9
+ # def name
10
+ # # ...
11
+ # end
12
+ #
13
+ # # Creates a one or more operations based on `formatted_value`.
14
+ # #
15
+ # # @return [#operator, Array<#operator>] Operation with this operator as the operation's `operator`.
16
+ # def operate_on(formatted_value)
17
+ # # ...
18
+ # end
19
+ # end
3
20
  #
4
- # A search operator.
5
21
  class Metasploit::Model::Search::Operator::Base < Metasploit::Model::Base
6
22
  include ActiveModel::Validations
7
23
  include Metasploit::Model::Search::Operator::Help
@@ -1,4 +1,24 @@
1
- # @abstract Operator that only returns a single operation from {#operate_on}.
1
+ # If all you want do is customize the name and operation `Class` that your custom operator class returns from
2
+ # `#operate_on`, then you can subclass {Metasploit::Model::Search::Operator::Single} instead of
3
+ # {Metasploit::Model::Search::Operator::Base}.
4
+ #
5
+ # class MyOperator < Metasploit::Model::Search::Operator::Single
6
+ # # Name of this operator. The name of the operator is matched to the string before the ':' in a formatted
7
+ # # operation.
8
+ # #
9
+ # # @return [Symbol]
10
+ # def name
11
+ # # ...
12
+ # end
13
+ #
14
+ # # `Class.name` of `Class` returned from {Metasploit::Model::Search::Operator::Single#operate_on}.
15
+ # #
16
+ # # @return [String] a `Class.name`
17
+ # def operation_class_name
18
+ # # ...
19
+ # end
20
+ # end
21
+ #
2
22
  class Metasploit::Model::Search::Operator::Single < Metasploit::Model::Search::Operator::Base
3
23
  #
4
24
  # CONSTANTS
@@ -1,4 +1,23 @@
1
- # A search query on a {#klass}. Parses query from {#formatted} string.
1
+ # Once {Metasploit::Model::Search::Operator search operators} are {Metasploit::Model::Search defined}, a formatted
2
+ # query, composed of space separated formatted operation, `<operator.name>:<formatted_value>`, can be parsed to
3
+ # produce a validatable query.
4
+ #
5
+ # query = Metasploit::Model::Search::Query.new(
6
+ # formatted: 'parts.number:1 parts.number:2 parts.uuid:EX,QR'
7
+ # )
8
+ #
9
+ # Operations using the same operator are unioned together, while operations with different operator are intersected
10
+ # together, so the above formatted query can be thought of as
11
+ # `(parts.number:1 || parts.number:2) && parts.uuid:EX,QR`.
12
+ #
13
+ # # Results
14
+ #
15
+ # Once a {Metasploit::Model::Search::Query} is defined, it needs to be converted to a data store specific visitor.
16
+ #
17
+ # Visitors for `ActiveRecord` are defined in
18
+ # {http://rubydoc.info/gems/metasploit_data_models/MetasploitDataModels/Search/Visitor MetasploitDataModels::Search::Visitor}.
19
+ #
20
+ # If you want to define your own visitors, you can subclass {Metasploit::Model::Visitation::Visitor}.
2
21
  class Metasploit::Model::Search::Query < Metasploit::Model::Base
3
22
  #
4
23
  # Attributes
@@ -0,0 +1,130 @@
1
+ module Metasploit
2
+ module Model
3
+ module Association
4
+ # Functions for turning a compact tree of compact as passed to
5
+ # {Metasploit::Model::Search::Association::ClassMethods#search_associations} into an expanded
6
+ # {Metasploit::Model::Search::Association::ClassMethods#search_association_tree}.
7
+ module Tree
8
+ # Expands a `compact` association into an expanded association tree.
9
+ #
10
+ # @param compact [Array, Hash{Symbol => Array,Hash,Symbol}, Symbol] a compact association as passed to
11
+ # {Metasploit::Model::Search::Association::ClassMethods#search_associations}.
12
+ # @return [Hash{Symbol => Hash,nil}]
13
+ def self.expand(compact)
14
+ case compact
15
+ when Array
16
+ compact.reduce({}) { |hash, association|
17
+ hash.merge(expand(association))
18
+ }
19
+ when Hash
20
+ child_by_parent = compact
21
+
22
+ child_by_parent.each_with_object({}) { |(parent, child), hash|
23
+ hash[parent] = expand(child)
24
+ }
25
+ when Symbol
26
+ association = compact
27
+
28
+ {association => nil}
29
+ end
30
+ end
31
+
32
+ # @note Unlike `Hash#deep_merge`, `second_expanded`'s values aren't favored over `first`'s values. Instead whichever
33
+ # side is present is used and if both `first` and `second_expanded` are present, then their `Hash#key`s' values are
34
+ # recursively merged.
35
+ #
36
+ # Merges two expanded association trees.
37
+ #
38
+ # @param first_expanded [nil, Hash{Symbol => nil,Hash}] An expanded association tree as from {expand}
39
+ # @param second_expanded [nil, Hash{Symbol => nil,Hash}] An expanded association tree as from {expand}
40
+ # @return [nil, Hash{Symbol => nil,Hash}] a new expanded association tree.
41
+ def self.merge(first_expanded, second_expanded)
42
+ if first_expanded.nil? && second_expanded.nil?
43
+ nil
44
+ elsif !first_expanded.nil? && second_expanded.nil?
45
+ first_expanded
46
+ elsif first_expanded.nil? && !second_expanded.nil?
47
+ second_expanded
48
+ else
49
+ first_keys = first_expanded.keys
50
+ key_set = Set.new(first_keys)
51
+
52
+ second_keys = second_expanded.keys
53
+ key_set.merge(second_keys)
54
+
55
+ key_set.each_with_object({}) do |key, merged|
56
+ first_child = first_expanded[key]
57
+ second_child = second_expanded[key]
58
+
59
+ merged[key] = merge(first_child, second_child)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Calculates association operators for the `expanded` association tree.
65
+ #
66
+ # @param expanded [Hash{Symbol => Hash,nil}, nil] An expanded association tree.
67
+ # @param options [Hash{Symbol => Class}]
68
+ # @option options [Class, #reflect_on_association] :class The `Class` on which the top-level key associations in
69
+ # `expanded` are declared.
70
+ # @return [Array<Metasploit::Model::Search::Operator::Association>]
71
+ def self.operators(expanded, options={})
72
+ expanded ||= {}
73
+
74
+ options.assert_valid_keys(:class)
75
+ klass = options.fetch(:class)
76
+
77
+ expanded.flat_map { |parent_association, child_tree|
78
+ reflection = reflect_on_association_on_class(parent_association, klass)
79
+ association_class = reflection.klass
80
+
81
+ association_search_with_operators = association_class.search_with_operator_by_name.each_value
82
+
83
+ child_tree_operators = operators(
84
+ child_tree,
85
+ class: reflection.klass
86
+ )
87
+
88
+ [association_search_with_operators, child_tree_operators].flat_map { |enumerator|
89
+ enumerator.map { |source_operator|
90
+ Metasploit::Model::Search::Operator::Association.new(
91
+ association: parent_association,
92
+ klass: klass,
93
+ source_operator: source_operator
94
+ )
95
+ }
96
+ }
97
+ }
98
+ end
99
+
100
+ private
101
+
102
+ # Return the association reflection for `association` on `klass`.
103
+ #
104
+ # @param association [Symbol] name of an association on `klass`.
105
+ # @param klass [#reflect_on_association] `Class` on which `association` is declared.
106
+ # @return [#klass] Association reflection that can give the `#klass` pointed to by the association.
107
+ # @raise [Metasploit::Model::Association::Error] if `association` is not declared on `klass`.
108
+ # @raise [NameError] if `klass` does not respond to `reflect_on_association`.
109
+ def self.reflect_on_association_on_class(association, klass)
110
+ begin
111
+ reflection = klass.reflect_on_association(association)
112
+ rescue NameError
113
+ raise NameError,
114
+ "#{self} does not respond to reflect_on_association. " \
115
+ "It can be added to ActiveModels by including Metasploit::Model::Association into the class."
116
+ end
117
+
118
+ unless reflection
119
+ raise Metasploit::Model::Association::Error.new(
120
+ model: klass,
121
+ name: association
122
+ )
123
+ end
124
+
125
+ reflection
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -2,6 +2,65 @@ module Metasploit
2
2
  module Model
3
3
  # DSL to define associations and attributes that can be searched. Making an association searchable, will expose
4
4
  # the attributes that association's class defined as searchable.
5
+ #
6
+ # # Operators
7
+ #
8
+ # Search operators define how to search against a given `Class`.
9
+ #
10
+ # ## Attributes
11
+ #
12
+ # Boolean, `Date`, `Integer`, and `String` attributes can be searched with
13
+ # {Metasploit::Model::Search::Attribute::ClassMethods#search_attribute search_attribute}. `Integer` and `String`
14
+ # attributes can be further restricted to a defined `Set` of values.
15
+ #
16
+ # class Part
17
+ # include Metasploit::Model::Search
18
+ #
19
+ # search_attribute :part,
20
+ # :integer
21
+ # end
22
+ #
23
+ # The above defines the `:part` operator on `Part`.
24
+ #
25
+ # ## Custom search operators
26
+ #
27
+ # If a search operator does not directly correspond to an attribute or a the attribute needs custom validation, then
28
+ # a custom {Metasploit::Model::Search::Operator operator class} can be setup as the search operator
29
+ #
30
+ # class Search::Operator::UUID
31
+ # def name
32
+ # :uuid
33
+ # end
34
+ # end
35
+ #
36
+ # class Part
37
+ # include Metasploit::Model::Search
38
+ #
39
+ # search_with Search::Operator::UUID
40
+ # end
41
+ #
42
+ # The above defines the `:uuid` operator on `Part`.
43
+ #
44
+ # ## Associations
45
+ #
46
+ # Search operators registered with
47
+ # {Metasploit::Model::Search::Attribute::ClassMethods#search_attribute search_attribute} or
48
+ # {Metasploit::Model::Search::With::ClassMethods#search_with search_with} on an associated `Class` can be searched
49
+ # with {Metasploit::Model::Search::Association::ClassMethods#search_association}:
50
+ #
51
+ # class Widget
52
+ # include Metasploit::Model::Search
53
+ #
54
+ # # declare parts association
55
+ #
56
+ # search_association :parts
57
+ # end
58
+ #
59
+ # The above will define the `:'parts.number'` and `:'parts.uuid'` operator on `Widget`.
60
+ #
61
+ # # Queries
62
+ #
63
+ # {include:Metasploit::Model::Search::Query}
5
64
  module Search
6
65
  extend ActiveSupport::Concern
7
66
 
@@ -25,33 +84,8 @@ module Metasploit
25
84
  @search_operator_by_name[operator.name] = operator
26
85
  end
27
86
 
28
- search_association_set.each do |association|
29
- begin
30
- reflection = reflect_on_association(association)
31
- rescue NameError
32
- raise NameError,
33
- "#{self} does not respond to reflect_on_association. " \
34
- "It can be added to ActiveModels by including Metasploit::Model::Association into the class."
35
- end
36
-
37
- unless reflection
38
- raise Metasploit::Model::Association::Error.new(:model => self, :name => association)
39
- end
40
-
41
- association_class = reflection.klass
42
-
43
- # don't use search_operator_by_name as association operators on operators won't work
44
- association_class.search_with_operator_by_name.each_value do |with_operator|
45
- # non-attribute operators on association are assumed not to work
46
- if with_operator.respond_to? :attribute
47
- association_operator = Metasploit::Model::Search::Operator::Association.new(
48
- :association => association,
49
- :attribute_operator => with_operator,
50
- :klass => self
51
- )
52
- @search_operator_by_name[association_operator.name] = association_operator
53
- end
54
- end
87
+ search_association_operators.each do |operator|
88
+ @search_operator_by_name[operator.name] = operator
55
89
  end
56
90
  end
57
91