1# Copyright (c) 2017, The MITRE Corporation. All rights reserved.
2# See LICENSE.txt for complete terms.
3
4# stdlib
5import functools
6
7# mixbox
8from mixbox import fields
9from mixbox import typedlist
10
11# cybox
12from cybox.core import Observable
13
14# internal
15import stix
16import stix.bindings.stix_common as common_binding
17import stix.bindings.stix_core as core_binding
18import stix.bindings.report as report_binding
19
20# deprecation warnings
21from stix.utils import deprecated
22
23# relative
24from .vocabs import VocabField
25from .information_source import InformationSource
26from .confidence import Confidence
27
28
29ALLOWED_SCOPE = ('inclusive', 'exclusive')
30
31
32def validate_scope(instance, value):
33    if not value:
34        return
35    elif value in ALLOWED_SCOPE:
36        return
37    else:
38        msg = "Scope must be one of {0}. Received '{1}'"
39        msg = msg.format(ALLOWED_SCOPE, value)
40        raise ValueError(msg)
41
42
43class GenericRelationship(stix.Entity):
44    _namespace = "http://stix.mitre.org/common-1"
45    _binding = common_binding
46    _binding_class = common_binding.GenericRelationshipType
47
48    confidence = fields.TypedField("Confidence", Confidence)
49    information_source = fields.TypedField("Information_Source", InformationSource)
50    relationship = VocabField("Relationship")
51
52    def __init__(self, confidence=None, information_source=None, relationship=None):
53        super(GenericRelationship, self).__init__()
54
55        self.confidence = confidence
56        self.information_source = information_source
57        self.relationship = relationship
58
59
60class RelatedPackageRef(GenericRelationship):
61    _namespace = "http://stix.mitre.org/common-1"
62    _binding = common_binding
63    _binding_class = common_binding.RelatedPackageRefType
64
65    idref = fields.IdrefField("idref")
66    timestamp = fields.DateTimeField("timestamp")
67
68    def __init__(self, idref=None, timestamp=None, confidence=None,
69                 information_source=None, relationship=None):
70
71        super(RelatedPackageRef, self).__init__(
72            confidence=confidence,
73            information_source=information_source,
74            relationship=relationship
75        )
76
77        self.idref = idref
78        self.timestamp = timestamp
79
80
81class GenericRelationshipEntity(stix.Entity):
82    _namespace = "http://stix.mitre.org/common-1"
83    _binding = common_binding
84    _binding_class = _binding.GenericRelationshipListType
85
86    _ALLOWED_SCOPE = ('inclusive', 'exclusive')
87
88    scope = fields.TypedField("scope")
89
90    def __init__(self, scope=None, *args):
91
92        super(GenericRelationshipEntity, self).__init__(*args)
93        self.scope = scope
94
95    def __nonzero__(self):
96        return (
97            super(GenericRelationshipList, self).__nonzero__() or
98            bool(self.scope)
99        )
100
101
102class GenericRelationshipList(stix.EntityList):
103    """Base class for concrete GenericRelationshipList types.
104
105    Note:
106        Subclasses must supply exactly one multiple TypedField.
107    """
108
109    _namespace = "http://stix.mitre.org/common-1"
110    _binding = common_binding
111    _binding_class = _binding.GenericRelationshipListType
112
113    scope = fields.TypedField("scope", preset_hook=validate_scope)
114
115    def __init__(self, scope=None, *args):
116        super(GenericRelationshipList, self).__init__(*args)
117        self.scope = scope
118
119    def __nonzero__(self):
120        return (super(GenericRelationshipList, self).__nonzero__() or
121                bool(self.scope))
122
123    @classmethod
124    def _dict_as_list(cls):
125        return False
126
127
128class _RelatedPackageList(typedlist.TypedList):
129    def __init__(self, *args):
130        super(_RelatedPackageList, self).__init__(type=RelatedPackageRef, *args)
131
132    def _fix_value(self, value):
133        from stix.core import STIXPackage
134
135        if isinstance(value, STIXPackage) and value.id_:
136            return RelatedPackageRef(idref=value.id_, timestamp=value.timestamp)
137
138        fmt = ("Cannot add type '{0}' to RelatedPackageRefs collection. "
139               "Expected RelatedPackageRef or STIXPackage")
140        error = fmt.format(type(value))
141        raise TypeError(error)
142
143    def _is_valid(self, value):
144        deprecated.warn(value)
145        return super(_RelatedPackageList, self)._is_valid(value)
146
147
148class RelatedPackageRefs(stix.EntityList):
149    _namespace = 'http://stix.mitre.org/common-1'
150    _binding = common_binding
151    _binding_class = common_binding.RelatedPackageRefsType
152
153    package = fields.TypedField(
154        name="Package_Reference",
155        type_=RelatedPackageRef,
156        multiple=True,
157        key_name="packages",
158        listfunc=_RelatedPackageList
159    )
160
161    @classmethod
162    def _dict_as_list(cls):
163        return False
164
165
166class _BaseRelated(GenericRelationship):
167    """A base class for related types.
168
169    This class is not a real STIX type and should not be directly instantiated.
170
171    Note:
172        Subclasses must supply a TypedField named `item`!
173    """
174
175    item = None  # override in subclass.
176
177    def __init__(self, item=None, confidence=None,
178                 information_source=None, relationship=None):
179
180        super(_BaseRelated, self).__init__(
181            confidence,
182            information_source,
183            relationship
184        )
185
186        self.item = item
187
188
189class RelatedCampaign(_BaseRelated):
190    _namespace = "http://stix.mitre.org/common-1"
191    _binding = common_binding
192    _binding_class = common_binding.RelatedCampaignType
193
194    # _BaseRelated requires an "item" field.
195    item = fields.TypedField("Campaign", type_="stix.campaign.Campaign")
196
197
198class RelatedCOA(_BaseRelated):
199    _namespace = "http://stix.mitre.org/common-1"
200    _binding = common_binding
201    _binding_class = common_binding.RelatedCourseOfActionType
202
203    # _BaseRelated requires an "item" field.
204    item = fields.TypedField("Course_Of_Action", type_="stix.coa.CourseOfAction")
205
206
207class RelatedExploitTarget(_BaseRelated):
208    _namespace = "http://stix.mitre.org/common-1"
209    _binding = common_binding
210    _binding_class = common_binding.RelatedExploitTargetType
211
212    # _BaseRelated requires an "item" field.
213    item = fields.TypedField("Exploit_Target", type_="stix.exploit_target.ExploitTarget")
214
215
216class RelatedIdentity(_BaseRelated):
217    _namespace = 'http://stix.mitre.org/common-1'
218    _binding = common_binding
219    _binding_class = common_binding.RelatedIdentityType
220
221    # _BaseRelated requires an "item" field.
222    item = fields.TypedField("Identity", type_="stix.common.identity.Identity", factory="stix.common.identity.IdentityFactory")
223
224
225class RelatedIncident(_BaseRelated):
226    _namespace = "http://stix.mitre.org/common-1"
227    _binding = common_binding
228    _binding_class = common_binding.RelatedIncidentType
229
230    # _BaseRelated requires an "item" field.
231    item = fields.TypedField("Incident", type_="stix.incident.Incident")
232
233class RelatedIndicator(_BaseRelated):
234    _namespace = "http://stix.mitre.org/common-1"
235    _binding = common_binding
236    _binding_class = common_binding.RelatedIndicatorType
237
238    # _BaseRelated requires an "item" field.
239    item = fields.TypedField("Indicator", type_="stix.indicator.Indicator")
240
241
242class RelatedObservable(_BaseRelated):
243    _namespace = "http://stix.mitre.org/common-1"
244    _binding = common_binding
245    _binding_class = common_binding.RelatedObservableType
246
247    # _BaseRelated requires an "item" field.
248    item = fields.TypedField("Observable", type_=Observable)
249
250class RelatedThreatActor(_BaseRelated):
251    _namespace = "http://stix.mitre.org/common-1"
252    _binding = common_binding
253    _binding_class = common_binding.RelatedThreatActorType
254
255    # _BaseRelated requires an "item" field.
256    item = fields.TypedField("Threat_Actor", type_="stix.threat_actor.ThreatActor")
257
258
259class RelatedTTP(_BaseRelated):
260    _namespace = "http://stix.mitre.org/common-1"
261    _binding = common_binding
262    _binding_class = common_binding.RelatedTTPType
263
264    # _BaseRelated requires an "item" field.
265    item = fields.TypedField("TTP", type_="stix.ttp.TTP")
266
267
268class RelatedPackage(_BaseRelated):
269    _namespace = "http://stix.mitre.org/stix-1"
270    _binding = core_binding
271    _binding_class = core_binding.RelatedPackageType
272
273    # _BaseRelated requires an "item" field.
274    item = fields.TypedField("Package", type_="stix.core.STIXPackage", preset_hook=deprecated.field)
275
276
277class RelatedReport(_BaseRelated):
278    _namespace = "http://stix.mitre.org/common-1"
279    _binding = common_binding
280    _binding_class = common_binding.RelatedReportType
281
282    # _BaseRelated requires an "item" field.
283    item = fields.TypedField("Report", type_="stix.report.Report")
284
285
286class RelatedCampaignRef(_BaseRelated):
287    _namespace = "http://stix.mitre.org/common-1"
288    _binding = common_binding
289    _binding_class = _binding.RelatedCampaignReferenceType
290
291    # _BaseRelated requires an "item" field.
292    item = fields.TypedField("Campaign", type_="stix.common.CampaignRef")
293
294
295class RelatedPackages(GenericRelationshipList):
296    _namespace = 'http://stix.mitre.org/stix-1'
297    _binding = core_binding
298    _binding_class = core_binding.RelatedPackagesType
299
300    related_package = fields.TypedField("Related_Package", RelatedPackage, multiple=True, key_name="related_packages")
301
302
303class RelatedReports(GenericRelationshipList):
304    _namespace = 'http://stix.mitre.org/Report-1'
305    _binding = report_binding
306    _binding_class = report_binding.RelatedReportsType
307
308    related_report = fields.TypedField("Related_Report", RelatedReport, multiple=True, key_name="related_reports")
309