Ejemplo n.º 1
0
 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())),
             },
         )
Ejemplo n.º 2
0
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})