def add_objects(self, bundle): self._verify_can_write() if isinstance(bundle, str): bundle = json.loads(bundle) for object in bundle.get("objects", []): self.objects.append(object) self.manifests.append( { "date_added": get_timestamp(), "id": object["id"], "media_type": "application/stix+json;version=2.1", "version": object.get("modified", object.get("created", get_timestamp())), }, )
def new_version(data, allow_custom=None, **kwargs): """ Create a new version of a STIX object, by modifying properties and updating the ``modified`` property. :param data: The object to create a new version of. Maybe a stix2 object or dict. :param allow_custom: Whether to allow custom properties on the new object. If True, allow them (regardless of whether the original had custom properties); if False disallow them; if None, propagate the preference from the original object. :param kwargs: The properties to change. Setting to None requests property removal. :return: The new object. """ is_versionable, is_21 = _is_versionable(data) if not is_versionable: raise ValueError( "cannot create new version of object of this type! " "Try a dictionary or instance of an SDO or SRO class.", ) if data.get('revoked'): raise RevokeError("new_version") try: new_obj_inner = copy.deepcopy(data._inner) except AttributeError: new_obj_inner = copy.deepcopy(data) # Make sure certain properties aren't trying to change # ID contributing properties of 2.1+ SCOs may also not change if a UUIDv5 # is in use (depending on whether they were used to create it... but they # probably were). That would imply an ID change, which is not allowed # across versions. sco_locked_props = [] if is_21 and isinstance(data, stix2.base._Observable): uuid_ = uuid.UUID(data["id"][-36:]) if uuid_.variant == uuid.RFC_4122 and uuid_.version == 5: sco_locked_props = data._id_contributing_properties unchangable_properties = set() for prop in itertools.chain(STIX_UNMOD_PROPERTIES, sco_locked_props): if prop in kwargs: unchangable_properties.add(prop) if unchangable_properties: raise UnmodifiablePropertyError(unchangable_properties) # Different versioning precision rules in STIX 2.0 vs 2.1, so we need # to know which rules to apply. precision_constraint = "min" if is_21 else "exact" cls = type(data) if 'modified' not in kwargs: old_modified = parse_into_datetime( data["modified"], precision="millisecond", precision_constraint=precision_constraint, ) new_modified = get_timestamp() new_modified = _fudge_modified(old_modified, new_modified, is_21) kwargs['modified'] = new_modified elif 'modified' in data: old_modified_property = parse_into_datetime( data.get('modified'), precision='millisecond', precision_constraint=precision_constraint, ) new_modified_property = parse_into_datetime( kwargs['modified'], precision='millisecond', precision_constraint=precision_constraint, ) if new_modified_property <= old_modified_property: raise InvalidValueError( cls, 'modified', "The new modified datetime cannot be before than or equal to the current modified datetime." "It cannot be equal, as according to STIX 2 specification, objects that are different " "but have the same id and modified timestamp do not have defined consumer behavior.", ) new_obj_inner.update(kwargs) # Set allow_custom appropriately if versioning an object. We will ignore # it for dicts. if isinstance(data, stix2.base._STIXBase): if allow_custom is None: new_obj_inner["allow_custom"] = data._allow_custom else: new_obj_inner["allow_custom"] = allow_custom # Exclude properties with a value of 'None' in case data is not an instance of a _STIXBase subclass return cls(**{k: v for k, v in new_obj_inner.items() if v is not None})