metasploit-model 0.25.7 → 0.26.1

Sign up to get free protection for your applications and to get access to all the features.
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