Skip to content

Commit df92770

Browse files
authored
Merge pull request #384 from oasis-open/365-versioned-classes
Validate custom type/property name formats
2 parents 8c4204d + 14540c0 commit df92770

31 files changed

+638
-364
lines changed

stix2/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
DEFAULT_VERSION = '2.0' # Default version will always be the latest STIX 2.X version
2424

2525
from .confidence import scales
26-
from .core import _collect_stix2_mappings, parse, parse_observable
2726
from .datastore import CompositeDataSource
2827
from .datastore.filesystem import (
2928
FileSystemSink, FileSystemSource, FileSystemStore,
@@ -38,6 +37,7 @@
3837
add_markings, clear_markings, get_markings, is_marked, remove_markings,
3938
set_markings,
4039
)
40+
from .parsing import _collect_stix2_mappings, parse, parse_observable
4141
from .patterns import (
4242
AndBooleanExpression, AndObservationExpression, BasicObjectPathComponent,
4343
BinaryConstant, BooleanConstant, EqualityComparisonExpression,

stix2/base.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@
22

33
import copy
44
import datetime as dt
5+
import re
56
import uuid
67

78
import simplejson as json
89
import six
910

11+
import stix2
1012
from stix2.canonicalization.Canonicalize import canonicalize
1113

1214
from .exceptions import (
1315
AtLeastOnePropertyError, DependentPropertiesError, ExtraPropertiesError,
1416
ImmutableError, InvalidObjRefError, InvalidValueError,
1517
MissingPropertiesError, MutuallyExclusivePropertiesError,
1618
)
19+
from .markings import _MarkingsMixin
1720
from .markings.utils import validate
18-
from .utils import NOW, find_property_index, format_datetime, get_timestamp
21+
from .utils import (
22+
NOW, PREFIX_21_REGEX, find_property_index, format_datetime, get_timestamp,
23+
)
1924
from .utils import new_version as _new_version
2025
from .utils import revoke as _revoke
2126

@@ -157,12 +162,23 @@ def __init__(self, allow_custom=False, **kwargs):
157162
custom_props = kwargs.pop('custom_properties', {})
158163
if custom_props and not isinstance(custom_props, dict):
159164
raise ValueError("'custom_properties' must be a dictionary")
160-
if not self._allow_custom:
161-
extra_kwargs = list(set(kwargs) - set(self._properties))
162-
if extra_kwargs:
163-
raise ExtraPropertiesError(cls, extra_kwargs)
164-
if custom_props:
165+
166+
extra_kwargs = list(set(kwargs) - set(self._properties))
167+
if extra_kwargs and not self._allow_custom:
168+
raise ExtraPropertiesError(cls, extra_kwargs)
169+
170+
# because allow_custom is true, any extra kwargs are custom
171+
if custom_props or extra_kwargs:
165172
self._allow_custom = True
173+
if isinstance(self, stix2.v21._STIXBase21):
174+
all_custom_prop_names = extra_kwargs
175+
all_custom_prop_names.extend(list(custom_props.keys()))
176+
for prop_name in all_custom_prop_names:
177+
if not re.match(PREFIX_21_REGEX, prop_name):
178+
raise InvalidValueError(
179+
self.__class__, prop_name,
180+
reason="Property name '%s' must begin with an alpha character." % prop_name,
181+
)
166182

167183
# Remove any keyword arguments whose value is None or [] (i.e. empty list)
168184
setting_kwargs = {}
@@ -305,6 +321,14 @@ def sort_by(element):
305321
return json.dumps(self, cls=STIXJSONEncoder, **kwargs)
306322

307323

324+
class _DomainObject(_STIXBase, _MarkingsMixin):
325+
pass
326+
327+
328+
class _RelationshipObject(_STIXBase, _MarkingsMixin):
329+
pass
330+
331+
308332
class _Observable(_STIXBase):
309333

310334
def __init__(self, **kwargs):

stix2/custom.py

Lines changed: 37 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,91 @@
11
from collections import OrderedDict
2-
import re
32

43
import six
54

6-
from .base import _cls_init, _Extension, _Observable, _STIXBase
7-
from .core import (
8-
STIXDomainObject, _register_marking, _register_object,
9-
_register_observable, _register_observable_extension,
5+
from .base import _cls_init
6+
from .parsing import (
7+
_register_marking, _register_object, _register_observable,
8+
_register_observable_extension,
109
)
11-
from .utils import TYPE_REGEX, get_class_hierarchy_names
1210

1311

14-
def _custom_object_builder(cls, type, properties, version):
15-
class _CustomObject(cls, STIXDomainObject):
12+
def _get_properties_dict(properties):
13+
try:
14+
return OrderedDict(properties)
15+
except TypeError as e:
16+
six.raise_from(
17+
ValueError(
18+
"properties must be dict-like, e.g. a list "
19+
"containing tuples. For example, "
20+
"[('property1', IntegerProperty())]",
21+
),
22+
e,
23+
)
1624

17-
if not re.match(TYPE_REGEX, type):
18-
raise ValueError(
19-
"Invalid type name '%s': must only contain the "
20-
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type,
21-
)
22-
elif len(type) < 3 or len(type) > 250:
23-
raise ValueError(
24-
"Invalid type name '%s': must be between 3 and 250 characters." % type,
25-
)
2625

27-
if not properties or not isinstance(properties, list):
28-
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
26+
def _custom_object_builder(cls, type, properties, version, base_class):
27+
prop_dict = _get_properties_dict(properties)
28+
29+
class _CustomObject(cls, base_class):
2930

3031
_type = type
31-
_properties = OrderedDict(properties)
32+
_properties = prop_dict
3233

3334
def __init__(self, **kwargs):
34-
_STIXBase.__init__(self, **kwargs)
35+
base_class.__init__(self, **kwargs)
3536
_cls_init(cls, self, kwargs)
3637

3738
_register_object(_CustomObject, version=version)
3839
return _CustomObject
3940

4041

41-
def _custom_marking_builder(cls, type, properties, version):
42-
class _CustomMarking(cls, _STIXBase):
42+
def _custom_marking_builder(cls, type, properties, version, base_class):
43+
prop_dict = _get_properties_dict(properties)
4344

44-
if not properties or not isinstance(properties, list):
45-
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
45+
class _CustomMarking(cls, base_class):
4646

4747
_type = type
48-
_properties = OrderedDict(properties)
48+
_properties = prop_dict
4949

5050
def __init__(self, **kwargs):
51-
_STIXBase.__init__(self, **kwargs)
51+
base_class.__init__(self, **kwargs)
5252
_cls_init(cls, self, kwargs)
5353

5454
_register_marking(_CustomMarking, version=version)
5555
return _CustomMarking
5656

5757

58-
def _custom_observable_builder(cls, type, properties, version, id_contrib_props=None):
58+
def _custom_observable_builder(cls, type, properties, version, base_class, id_contrib_props=None):
5959
if id_contrib_props is None:
6060
id_contrib_props = []
6161

62-
class _CustomObservable(cls, _Observable):
63-
64-
if not re.match(TYPE_REGEX, type):
65-
raise ValueError(
66-
"Invalid observable type name '%s': must only contain the "
67-
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type,
68-
)
69-
elif len(type) < 3 or len(type) > 250:
70-
raise ValueError("Invalid observable type name '%s': must be between 3 and 250 characters." % type)
71-
72-
if not properties or not isinstance(properties, list):
73-
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
74-
75-
if version == "2.0":
76-
# If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties
77-
for prop_name, prop in properties:
78-
if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)):
79-
raise ValueError(
80-
"'%s' is named like an object reference property but "
81-
"is not an ObjectReferenceProperty." % prop_name,
82-
)
83-
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
84-
'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
85-
raise ValueError(
86-
"'%s' is named like an object reference list property but "
87-
"is not a ListProperty containing ObjectReferenceProperty." % prop_name,
88-
)
89-
else:
90-
# If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties
91-
for prop_name, prop in properties:
92-
if prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)):
93-
raise ValueError(
94-
"'%s' is named like a reference property but "
95-
"is not a ReferenceProperty." % prop_name,
96-
)
97-
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
98-
'ReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
99-
raise ValueError(
100-
"'%s' is named like a reference list property but "
101-
"is not a ListProperty containing ReferenceProperty." % prop_name,
102-
)
62+
prop_dict = _get_properties_dict(properties)
63+
64+
class _CustomObservable(cls, base_class):
10365

10466
_type = type
105-
_properties = OrderedDict(properties)
67+
_properties = prop_dict
10668
if version != '2.0':
10769
_id_contributing_properties = id_contrib_props
10870

10971
def __init__(self, **kwargs):
110-
_Observable.__init__(self, **kwargs)
72+
base_class.__init__(self, **kwargs)
11173
_cls_init(cls, self, kwargs)
11274

11375
_register_observable(_CustomObservable, version=version)
11476
return _CustomObservable
11577

11678

117-
def _custom_extension_builder(cls, observable, type, properties, version):
118-
119-
try:
120-
prop_dict = OrderedDict(properties)
121-
except TypeError as e:
122-
six.raise_from(
123-
ValueError(
124-
"Extension properties must be dict-like, e.g. a list "
125-
"containing tuples. For example, "
126-
"[('property1', IntegerProperty())]",
127-
),
128-
e,
129-
)
79+
def _custom_extension_builder(cls, observable, type, properties, version, base_class):
80+
prop_dict = _get_properties_dict(properties)
13081

131-
class _CustomExtension(cls, _Extension):
82+
class _CustomExtension(cls, base_class):
13283

13384
_type = type
13485
_properties = prop_dict
13586

13687
def __init__(self, **kwargs):
137-
_Extension.__init__(self, **kwargs)
88+
base_class.__init__(self, **kwargs)
13889
_cls_init(cls, self, kwargs)
13990

14091
_register_observable_extension(observable, _CustomExtension, version=version)

stix2/datastore/filesystem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111
from stix2 import v20, v21
1212
from stix2.base import _STIXBase
13-
from stix2.core import parse
1413
from stix2.datastore import (
1514
DataSink, DataSource, DataSourceError, DataStoreMixin,
1615
)
1716
from stix2.datastore.filters import Filter, FilterSet, apply_common_filters
17+
from stix2.parsing import parse
1818
from stix2.utils import format_datetime, get_type_from_id
1919

2020

stix2/datastore/memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
from stix2 import v20, v21
99
from stix2.base import _STIXBase
10-
from stix2.core import parse
1110
from stix2.datastore import DataSink, DataSource, DataStoreMixin
1211
from stix2.datastore.filters import FilterSet, apply_common_filters
12+
from stix2.parsing import parse
1313

1414

1515
def _add(store, stix_data, allow_custom=True, version=None):

stix2/datastore/taxii.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
from stix2 import v20, v21
66
from stix2.base import _STIXBase
7-
from stix2.core import parse
87
from stix2.datastore import (
98
DataSink, DataSource, DataSourceError, DataStoreMixin,
109
)
1110
from stix2.datastore.filters import Filter, FilterSet, apply_common_filters
11+
from stix2.parsing import parse
1212
from stix2.utils import deduplicate
1313

1414
try:

stix2/environment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import logging
55
import time
66

7-
from .core import parse as _parse
87
from .datastore import CompositeDataSource, DataStoreMixin
8+
from .parsing import parse as _parse
99
from .utils import STIXdatetime, parse_into_datetime
1010

1111
logger = logging.getLogger(__name__)

0 commit comments

Comments
 (0)