ruby_stix 0.0.2-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 112ae61c03f537dfd3b01d0dffaeedd15ad82300
4
+ data.tar.gz: 695c4969f7eca8b727d458c77ae76597a8335782
5
+ SHA512:
6
+ metadata.gz: c1ad8b82de21c3ddc359aaf98d7b0ead9f06dbe34b795efd2934c4730b4d741d4c72cf7d7e4279503aa9594b2b21d4d4af1ba6a807c27bffb3d3fc0bfd31777a
7
+ data.tar.gz: 6a7731bf85bdd57817956d43c1c7e667643114fa245d39a6d6eb20e76670568ba7046d43fde478fa3875889d6e2e98c1c7f860931129167468ddb0689b1de1c2
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ jruby-1.7.9
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby_stix.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 John Wunder
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # StixRuby
2
+
3
+ Bindings for generating and parsing STIX documents in JRuby. It relies on java-stix.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ruby_stix', :git => "http://github.com/johnwunder/ruby-stix.git"
10
+
11
+ And then execute:
12
+
13
+ $ bundle install
14
+
15
+ Or, clone this repository, build the gem, and install it manually as:
16
+
17
+ $ rake build
18
+ $ gem install pkg/ruby_stix.gem
19
+
20
+ ## Usage
21
+
22
+ TODO
23
+
24
+ ## Contributing
25
+
26
+ 1. Fork it
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
Binary file
@@ -0,0 +1,307 @@
1
+ require 'active_support/inflector'
2
+ require 'set'
3
+
4
+ java_import 'javax.xml.datatype.DatatypeFactory'
5
+
6
+ ActiveSupport::Inflector.inflections do |inflect|
7
+ inflect.acronym 'TTP'
8
+ inflect.acronym 'TTPs'
9
+ end
10
+
11
+ class Java::OrgMitre::ApiHelper
12
+
13
+ BLACKLIST = ["getClass", "hashCode", "equals", "toString", "notify", "notifyAll", "wait"]
14
+
15
+ def initialize(*args)
16
+ # Every time we create an object, we try to annotate that object's class. If the class has been annotated already,
17
+ # this does nothing.
18
+ annotate_class!
19
+
20
+ # Call the super constructor to create the java object backing
21
+ super()
22
+
23
+ # We add several options for creating new objects. This method figures out what the particular object we're creating
24
+ # supports and then processes the arguments. It will throw an error if invalid arguments are passed.
25
+ process_constructor_args(*args)
26
+
27
+ # (TODO: This may not be the right place to put this?)
28
+ # Generate an ID if the object supports it and we didn't set something manually
29
+ self.generate_id! if should_create_id?
30
+ end
31
+
32
+ # Returns whether or not a new object should have an auto-generated ID. The criteria are:
33
+ # 1. It responds to "id" and "idref" (i.e. is a STIX-idable object)
34
+ # 2. It doesn't have "suppress_id" set
35
+ # 3. There's no id or idref set already
36
+ def should_create_id?
37
+ self.respond_to?(:id) && self.respond_to?(:idref) && !self.class.suppress_id? && self.idref.nil? && self.id.nil?
38
+ end
39
+
40
+ # Process arguments that are passed to the constructor
41
+ # There are a couple options here:
42
+
43
+ # 1. A hash is passed, which will call appropriate setter methods to set each value to the key
44
+ # 2. A string is passed, which will set the string value of the element
45
+ # 3. A string and hash are passed, which will do both
46
+ # 4. Nothing is passed, which will just create the object
47
+ def process_constructor_args(*args)
48
+ # If two arguments are passed, option #3 is the winner. The first argument will set the value and the second argument is the kv hash
49
+ if args.length == 2 && self.respond_to?("value=")
50
+ self.value = args[0]
51
+ args = args[1]
52
+ # If one argument is passed and it's not a hash, try to set it as the value (likely it's a string) and use an empty hash as the kv hash
53
+ elsif args.length == 1 && !args[0].kind_of?(Hash) && self.respond_to?("value=")
54
+ self.value = args[0]
55
+ args = {}
56
+ # If one argument is passed and it's a hash, use that as the kv hash
57
+ elsif args.length == 1 && args[0].kind_of?(Hash)
58
+ args = args[0]
59
+ # If nothing was passed, use an empty hash as the kv hash
60
+ elsif args.length == 0
61
+ args = {}
62
+ elsif args.first.kind_of?(Array)
63
+ handle_array_argument(self, args.first)
64
+ args = {}
65
+ # Finally, throw an error if the arguments are anything else
66
+ else
67
+ raise "Invalid arguments to construct #{self.class.to_s}: #{args.inspect}"
68
+ end
69
+
70
+ # If key/value pairs were passed, use them
71
+ process_args(args).each do |key, value|
72
+ process_single_argument(key, value)
73
+ end
74
+ end
75
+
76
+ def process_single_argument(k, v)
77
+ # If we respond to the setter, call it. This effectively allows Java-style keyword argument names to be used
78
+ if self.respond_to?("set#{k}")
79
+ self.send("set#{k}", v)
80
+ # If we respond to the Ruby setter, call it. This allows Ruby-style keyword argument names to be used
81
+ elsif self.respond_to?("#{k}=")
82
+ self.send("#{k}=", v)
83
+ # If the value is an array, we can handle it a little differently
84
+ # Note that some array arguments might get caught by the setter
85
+ elsif v.kind_of?(Array)
86
+ # Find the Java method name even if we used a Ruby-style name. This is imperfect so may throw errors.
87
+ java_method_name = java_method_name_for_key(k)
88
+
89
+ expected_type = nil
90
+
91
+ # Add each value individually to the list
92
+ v.each do |value|
93
+ argument_type = find_generic_argument_for(java_method_name)
94
+
95
+ value = auto_create_object(argument_type, value)
96
+
97
+ # Finally, set the value
98
+ self.send(java_method_name).add(value)
99
+ end
100
+ else
101
+ raise ArgumentError.new("Invalid argument to construct #{self.class.to_s}: `#{k}`")
102
+ end
103
+ end
104
+
105
+ def java_method_name_for_key(k)
106
+ if self.respond_to?("get#{k}")
107
+ "get#{k}"
108
+ elsif self.respond_to?("get#{to_java_name(k)}")
109
+ "get#{to_java_name(k)}"
110
+ else
111
+ raise "Unable to find corresponding java method for #{k}"
112
+ end
113
+ end
114
+
115
+ # Finds the expected class for a list by parsing it out of the Java signature. This kind of blows but the way Java
116
+ # implements generics (type erasure) means the JRuby code does not have access to the generic type.
117
+ def find_generic_argument_for(k)
118
+ return eval(self.java_class.java_method(k).to_generic_string.match(/<(.+)>/)[1])
119
+ end
120
+
121
+ # Convert a Ruby-style method name (lower snake) to a Java-style method name (camel)
122
+ # This is imperfect, really I would like to re-use the JRuby logic but don't know how.
123
+ def to_java_name(string)
124
+ string.to_s.camelize
125
+ end
126
+
127
+ # Generate a random ID. Uses the ID namespace if it's been set.
128
+ def generate_id!
129
+ self.id = StixRuby.generate_id(self.class.to_s.split('::').last.gsub('Type', '').downcase)
130
+ end
131
+
132
+ # Theoretically method_missing might be used for more, but currently it just tries to catch "add_"
133
+ # calls and direct them to the appropriate child.
134
+ # TODO: Should we just define these methods manually by iterating over all methods and finding lists?
135
+ def method_missing(method_name, *args)
136
+ # Catch us trying to "add_observable" to "ObservablesType" and correctly handle it
137
+ if matches = method_name.to_s.match(/^add_(.+)$/)
138
+ if matches[1] && self.respond_to?(matches[1].pluralize)
139
+ java_method_name = java_method_name_for_key(matches[1].pluralize)
140
+ if respond_to?(java_method_name)
141
+ # If the method is a list, try to add the object
142
+ if send(java_method_name).class == Java::JavaUtil::ArrayList # Need to do an equality check on the class because sometimes other classes masquerade as lists
143
+ argument_type = find_generic_argument_for(java_method_name)
144
+ self.send(java_method_name).add(auto_create_object(argument_type, args.first))
145
+ elsif send(java_method_name).nil?
146
+ # Use the setter...
147
+ send(java_method_name.gsub(/^get/, "set"), args)
148
+ else
149
+ # We already have an item in the list, so just add the new one
150
+ intermediate = send(java_method_name)
151
+ argument_type = intermediate.find_generic_argument_for(java_method_name)
152
+ intermediate.send(java_method_name).add(auto_create_object(argument_type, args.first))
153
+ end
154
+ else
155
+ super
156
+ end
157
+ else
158
+ super
159
+ end
160
+ else
161
+ super
162
+ end
163
+ end
164
+
165
+ # This is a callback that children can override to add fancy helpers to constructor arguments
166
+ # Here though it's a pass-through
167
+ def process_args(args)
168
+ args
169
+ end
170
+
171
+ # Some behavior to determine whether to generate an ID
172
+ def self.suppress_id
173
+ @suppress_id = true
174
+ end
175
+
176
+ # TODO: This would be better without the multiple checks...
177
+ def self.suppress_id?
178
+ @suppress_id == true || (superclass.respond_to?(:suppress_id) && superclass.suppress_id?)
179
+ end
180
+
181
+ def self.annotate!
182
+ # Mark us as annotated
183
+ @annotated = true
184
+
185
+ # Annotate superclass if it's ok with that
186
+ self.superclass.annotate! if self.superclass.respond_to?(:annotate!)
187
+
188
+ # JRuby->Ruby name translation is not perfect and, for example, screws up "TTP"
189
+ # This will go through all methods and correct the ruby methods
190
+ StixRuby::IRREGULARS.each do |irregular_pattern, correct_pattern|
191
+ self.instance_methods.select {|m| m.to_s =~ irregular_pattern}.each do |irregular|
192
+ alias_method irregular.to_s.gsub(irregular_pattern, correct_pattern), irregular
193
+ end
194
+ end
195
+
196
+ # Hooks into both Ruby and Java-style setters and makes them a little more intelligent by trying
197
+ # to handle arrays appropriately and call constructors automatically when necessary
198
+ self.setter_methods.each do |method_name, java_method|
199
+ # Find the type of the argument and the name of the method
200
+ argument_type = java_method.argument_types.first.ruby_class
201
+
202
+ # Do not annotate this method if it's already annotated or has a basic value constructor
203
+ next if argument_type == Java::JavaLang::Object || self.annotated_method?(method_name)
204
+
205
+ # Mark this method as annotated
206
+ self.annotated_method(method_name)
207
+
208
+ # Alias the raw version
209
+ alias_method method_name + "Raw", method_name
210
+
211
+ # Re-define the method
212
+ define_method method_name, ->(*args) do
213
+ # Must have at least one argument
214
+ raise ArgumentError.new("Wrong number of arguments (0 for 1)") if args.nil? || args.length == 0
215
+
216
+ # Pass the argument to the raw setter if it's already of the correct type
217
+ if args.first.kind_of?(argument_type)
218
+ send(method_name + "Raw", *args)
219
+ # This handles cases where we have essentially a wrapper element around an array
220
+ # and allows us to just set the array
221
+ elsif args.first.kind_of?(Array)
222
+ new_obj = argument_type.new
223
+ handle_array_argument(new_obj, args.first)
224
+ send(method_name + "Raw", new_obj)
225
+ else
226
+ # Try to auto-create the object (magic happens here)
227
+ object = auto_create_object(argument_type, args.first)
228
+
229
+ send(method_name + "Raw", object)
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ # Returns the Ruby or Java setter method name and the corresponding java setter method reference
236
+ def self.setter_methods
237
+ self.java_class.java_instance_methods.select {|method| method.name =~ /^set/ && !(method.name =~ /Raw$/)}.map { |method|
238
+ # If the method accepts more than one argument, ignore it
239
+ if method.argument_types.length == 1
240
+ methods = [[method.name, method]]
241
+ methods << [ruby_name(method.name), method] if self.instance_methods.find {|m| m.to_s == ruby_name(method.name)}
242
+ ruby_setter = ruby_name(method.name.gsub("set", "") + "=")
243
+ methods << [ruby_setter, method] if self.instance_methods.find {|m| m.to_s == ruby_setter}
244
+ methods
245
+ else
246
+ nil
247
+ end
248
+ }.compact.flatten(1)
249
+ end
250
+
251
+ def handle_array_argument(assign_to, argument)
252
+ # Create the array destination
253
+ # Try to find the appropriate getter method for the array
254
+ getter = assign_to.java_class.java_instance_methods.reject {|m| BLACKLIST.include?(m.name) || m.return_type != java.util.List.java_class }
255
+ raise "Unable to automatically determine array container, please explicitly specify it" if getter.length != 1
256
+ getter = getter.first.name
257
+ array = assign_to.send(getter)
258
+ getter_reference = assign_to.java_class.java_method(getter).to_generic_string.match(/<(.+)>/)[1]
259
+ expected_type = eval(getter_reference)
260
+ argument.each {|item|
261
+ array.add(auto_create_object(expected_type, item))
262
+ }
263
+ end
264
+
265
+ def auto_create_object(argument_type, arg)
266
+ if arg.kind_of?(argument_type)
267
+ arg
268
+ elsif argument_type.respond_to?(:from_value)
269
+ argument_type.from_value(arg)
270
+ # Handle an array argument
271
+ # JAXB dates are really F'd up, so autoconvert them
272
+ elsif argument_type == javax.xml.datatype.XMLGregorianCalendar
273
+ calendar = java.util.GregorianCalendar.new
274
+ calendar.setTime(arg.to_java)
275
+ DatatypeFactory.newInstance.newXMLGregorianCalendar(calendar)
276
+ else
277
+ argument_type.new(arg)
278
+ end
279
+ end
280
+
281
+ def self.ruby_name(method)
282
+ method.underscore
283
+ end
284
+
285
+ # A bunch of crap for detecting when things have already been annotated
286
+ def self.annotated?
287
+ @annotated == true
288
+ end
289
+
290
+ def annotated?
291
+ self.class.annotated?
292
+ end
293
+
294
+ def self.annotated_method?(name)
295
+ @annotated_methods ||= Set.new
296
+ @annotated_methods.include?(name) || (self.superclass.respond_to?(:annotated_method?) && self.superclass.annotated_method?(name))
297
+ end
298
+
299
+ def self.annotated_method(method)
300
+ @annotated_methods ||= Set.new
301
+ @annotated_methods.add(method)
302
+ end
303
+
304
+ def annotate_class!
305
+ self.class.annotate! unless annotated?
306
+ end
307
+ end
@@ -0,0 +1,3 @@
1
+ class Java::OrgMitreCyboxCommon::BaseObjectPropertyType
2
+ suppress_id
3
+ end
@@ -0,0 +1,28 @@
1
+ class Java::OrgMitreStixIndicator::IndicatorType
2
+ def item=(val)
3
+ if val.kind_of?(org.mitre.cybox.core.ObservableType)
4
+ self.observable = val
5
+ elsif val.kind_of?(org.mitre.cybox.core.ObjectType)
6
+ self.observable = org.mitre.cybox.core.ObservableType.new(:object => val)
7
+ elsif val.kind_of?(org.mitre.cybox.core.EventType)
8
+ self.observable = org.mitre.cybox.core.ObservableType.new(:event => val)
9
+ elsif val.kind_of?(org.mitre.cybox.common.ObjectPropertiesType)
10
+ self.observable = org.mitre.cybox.core.ObservableType.new(:object => org.mitre.cybox.core.ObjectType.new(:properties => val))
11
+ elsif val.kind_of?(Hash) && val[:operator]
12
+ self.composite_indicator_expression = process_composition(val)
13
+ else
14
+ raise "Unknown item type: #{val.class}"
15
+ end
16
+ end
17
+
18
+ def process_composition(composition_hash)
19
+ operator = org.mitre.stix.indicator.OperatorTypeEnum.from_value(composition_hash[:operator].to_s.upcase)
20
+ composition = org.mitre.stix.indicator.CompositeIndicatorExpressionType.new(:operator => operator)
21
+ composition_hash[:items].each do |item|
22
+ indicator = item.kind_of?(org.mitre.stix.common.IndicatorBaseType) ? item : self.class.new(:item => item)
23
+ composition.add_indicator(indicator)
24
+ end
25
+
26
+ return composition
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ class Java::OrgMitreCyboxCore::ObjectType
2
+ def add_related_object(object, relationship = nil)
3
+ self.related_objects ||= org.mitre.cybox.core.RelatedObjectsType.new
4
+
5
+ if object.kind_of?(org.mitre.cybox.core.RelatedObjectType)
6
+ self.related_objects.add_related_object(object)
7
+ else
8
+ related_object = org.mitre.cybox.core.RelatedObjectType.new(:idref => object.id)
9
+ if relationship
10
+ if relationship.kind_of?(String)
11
+ # Ugh, why does Java throw an exception here?
12
+ begin
13
+ enum = org.mitre.cybox.vocabularies.ObjectRelationshipEnum10.from_value(relationship)
14
+ related_object.relationship = org.mitre.cybox.vocabularies.ObjectRelationshipVocab10.new(:value => enum.value)
15
+ rescue
16
+ related_object.relationship = org.mitre.cybox.common.ControlledVocabularyStringType.new(:value => relationship)
17
+ end
18
+ else
19
+ related_object.relationship = relationship
20
+ end
21
+ end
22
+
23
+ self.related_objects.add_related_object(related_object)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ class Java::OrgMitreCyboxCore::ObservableType
2
+ def item=(val)
3
+ if val.kind_of?(org.mitre.cybox.core.ObjectType)
4
+ self.object = val
5
+ elsif val.kind_of?(org.mitre.cybox.core.EventType)
6
+ self.event = val
7
+ elsif val.kind_of?(org.mitre.cybox.common.ObjectPropertiesType)
8
+ self.object = Java::OrgMitreCyboxCore::ObjectType.new(:properties => val)
9
+ elsif val.kind_of?(Hash) && val[:operator]
10
+ self.observable_composition = process_composition(val)
11
+ else
12
+ raise "Unknown item type: #{val.class}"
13
+ end
14
+ end
15
+
16
+ def process_composition(composition_hash)
17
+ operator = org.mitre.cybox.core.OperatorTypeEnum.from_value(composition_hash[:operator].to_s.upcase)
18
+ composition = Java::OrgMitreCyboxCore::ObservableCompositionType.new(:operator => operator)
19
+ composition_hash[:items].each do |item|
20
+ observable = item.kind_of?(org.mitre.cybox.core.ObservableType) ? item : self.class.new(:item => item)
21
+ composition.add_observable(observable)
22
+ end
23
+
24
+ return composition
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ class Java::OrgMitreCyboxCore::ObservablesType
2
+ include StixRuby::Marshall
3
+
4
+ def process_args(args)
5
+ args[:cybox_update_version] = "1"
6
+ args
7
+ end
8
+ end
@@ -0,0 +1,56 @@
1
+ class Java::OrgMitreStixCore::STIXType
2
+ include StixRuby::Marshall
3
+
4
+ def add_observable(observable)
5
+ self.observables ||= org.mitre.cybox.core.ObservablesType.new(:cybox_major_version => '2', :cybox_minor_version => '1')
6
+ self.observables.add_observable(observable)
7
+ end
8
+
9
+ def add_campaign(campaign)
10
+ self.campaigns ||= org.mitre.stix.core.CampaignsType.new
11
+ self.campaigns.add_campaign(campaign)
12
+ end
13
+
14
+ def add_course_of_action(coa)
15
+ self.courses_of_action ||= org.mitre.stix.core.CoursesOfActionType.new
16
+ self.courses_of_action.course_of_actions.add(coa)
17
+ end
18
+
19
+ def add_exploit_target(et)
20
+ self.exploit_targets ||= org.mitre.stix.common.ExploitTargetsType.new
21
+ self.exploit_targets.add_exploit_target(et)
22
+ end
23
+
24
+ def add_incident(incident)
25
+ self.incidents ||= org.mitre.stix.core.IncidentsType.new
26
+ self.incidents.add_incident(incident)
27
+ end
28
+
29
+ def add_indicator(indicator)
30
+ self.indicators ||= org.mitre.stix.core.IndicatorsType.new
31
+ self.indicators.add_indicator(indicator)
32
+ end
33
+
34
+ def add_threat_actor(ta)
35
+ self.threat_actors ||= org.mitre.stix.core.ThreatActorsType.new
36
+ self.threat_actors.add_threat_actor(ta)
37
+ end
38
+
39
+ def ttps
40
+ self.getTTPs
41
+ end
42
+
43
+ def ttps=(val)
44
+ self.setTTPs(val)
45
+ end
46
+
47
+ def add_ttp(ttp)
48
+ self.ttps ||= org.mitre.stix.core.TTPsType.new
49
+ self.ttps.getTTPS.add(ttp)
50
+ end
51
+
52
+ def process_args(args)
53
+ args[:version] ||= "1.0.1"
54
+ args
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ require 'ruby_stix'
2
+
3
+ require 'ruby_stix/marshall'
4
+
5
+ require 'ruby_stix/api/api_helper'
6
+ require 'ruby_stix/api/indicator'
7
+ require 'ruby_stix/api/observable'
8
+ require 'ruby_stix/api/object'
9
+ require 'ruby_stix/api/observables_type'
10
+ require 'ruby_stix/api/stix_type'
11
+ require 'ruby_stix/api/base_object_property_type'