stidantic 0.1.3__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of stidantic might be problematic. Click here for more details.

stidantic/sdo.py CHANGED
@@ -1,12 +1,18 @@
1
- from datetime import datetime
2
- from typing import Any, Literal, Annotated, Self
3
- from typing_extensions import deprecated
4
- from annotated_types import Ge, Gt, Le
1
+ from typing import Annotated, Any, Literal, Self
5
2
 
6
- from pydantic import Field
3
+ from annotated_types import Ge, Le
4
+ from pydantic import AfterValidator, Field
7
5
  from pydantic.functional_validators import model_validator
6
+ from typing_extensions import deprecated
8
7
 
9
- from stidantic.types import Identifier, StixDomain, KillChainPhase, ExternalReference
8
+ from stidantic.types import (
9
+ ExternalReference,
10
+ Identifier,
11
+ KillChainPhase,
12
+ StixDomain,
13
+ StixTimestamp,
14
+ )
15
+ from stidantic.validators import identifier_of_type
10
16
  from stidantic.vocab import OpinionEnum
11
17
 
12
18
 
@@ -66,12 +72,12 @@ class Campaign(StixDomain):
66
72
  # A summary property of data from sightings and other data that may or may not be available in STIX.
67
73
  # If new sightings are received that are earlier than the first seen timestamp,
68
74
  # the object may be updated to account for the new data.
69
- first_seen: datetime | None = None
75
+ first_seen: StixTimestamp | None = None
70
76
  # The time that this Campaign was last seen.
71
77
  # A summary property of data from sightings and other data that may or may not be available in STIX.
72
78
  # If new sightings are received that are later than the last seen timestamp,
73
79
  # the object may be updated to account for the new data.
74
- last_seen: datetime | None = None
80
+ last_seen: StixTimestamp | None = None
75
81
  # The Campaign’s primary goal, objective, desired outcome, or intended effect
76
82
  # — what the Threat Actor or Intrusion Set hopes to accomplish with this Campaign.
77
83
  objective: str | None = None
@@ -113,7 +119,7 @@ class CourseOfAction(StixDomain):
113
119
  # potentially including its purpose and its key characteristics.
114
120
  description: str | None = None
115
121
  # A reserved property that serves as a placeholder for future inclusion of machine automatable courses of action.
116
- # action: str | None = None
122
+ # action: str | None = None # noqa: ERA001
117
123
 
118
124
 
119
125
  # 4.4 Grouping
@@ -236,12 +242,12 @@ class Indicator(StixDomain):
236
242
  # this object’s creation.
237
243
  pattern_version: str | None = None
238
244
  # The time from which this Indicator is considered a valid indicator of the behaviors it is related or represents.
239
- valid_from: datetime
245
+ valid_from: StixTimestamp
240
246
  # The time at which this Indicator should no longer be considered a valid indicator of the behaviors it is
241
247
  # related to or represents.
242
248
  # If the valid_until property is omitted, then there is no constraint on the latest time for which the
243
249
  # Indicator is valid.
244
- valid_until: datetime | None = None
250
+ valid_until: StixTimestamp | None = None
245
251
  # The kill chain phase(s) to which this Indicator corresponds.
246
252
  kill_chain_phases: list[KillChainPhase] | None = None
247
253
 
@@ -251,9 +257,7 @@ class Indicator(StixDomain):
251
257
  The valid_until property MUST be greater than the timestamp in the valid_from property.
252
258
  """
253
259
  if self.valid_from and self.valid_until and self.valid_from > self.valid_until:
254
- raise ValueError(
255
- "The valid_until property MUST be greater than the timestamp in the valid_from property"
256
- )
260
+ raise ValueError("The valid_until property MUST be greater than the timestamp in the valid_from property")
257
261
  return self
258
262
 
259
263
 
@@ -282,9 +286,9 @@ class Infrastructure(StixDomain):
282
286
  # The list of Kill Chain Phases for which this Infrastructure is used.
283
287
  kill_chain_phases: list[KillChainPhase] | None = None
284
288
  # The time that this Infrastructure was first seen performing malicious activities.
285
- first_seen: datetime | None = None
289
+ first_seen: StixTimestamp | None = None
286
290
  # The time that this Infrastructure was last seen performing malicious activities.
287
- last_seen: datetime | None = None
291
+ last_seen: StixTimestamp | None = None
288
292
 
289
293
  @model_validator(mode="after")
290
294
  def validate_last_seen_after_first_seen(self) -> Self:
@@ -329,12 +333,12 @@ class IntrusionSet(StixDomain):
329
333
  # A summary property of data from sightings and other data that may or may not be available in STIX.
330
334
  # If new sightings are received that are earlier than the first seen timestamp, the object may be updated to
331
335
  # account for the new data.
332
- first_seen: datetime | None = None
336
+ first_seen: StixTimestamp | None = None
333
337
  # The time that this Intrusion Set was last seen.
334
338
  # This property is a summary property of data from sightings and other data that may or may not be available.
335
339
  # If new sightings are received that are later than the last seen timestamp, the object may be updated to
336
340
  # account for the new data.
337
- last_seen: datetime | None = None
341
+ last_seen: StixTimestamp | None = None
338
342
  # The high-level goals of this Intrusion Set, namely, what are they trying to do.
339
343
  # For example, they may be motivated by personal gain, but their goal is to steal credit card numbers.
340
344
  # To do this, they may execute specific Campaigns that have detailed objectives like compromising point of sale
@@ -423,14 +427,8 @@ class Location(StixDomain):
423
427
  - country
424
428
  - latitude and longitude
425
429
  """
426
- if (
427
- not self.region
428
- and not self.country
429
- and not (self.latitude and self.longitude)
430
- ):
431
- raise ValueError(
432
- "At least one of region, country, or both latitude and longitude MUST be provided"
433
- )
430
+ if not self.region and not self.country and not (self.latitude and self.longitude):
431
+ raise ValueError("At least one of region, country, or both latitude and longitude MUST be provided")
434
432
 
435
433
  if self.latitude and not self.longitude:
436
434
  raise ValueError("If latitude is present, longitude MUST be present")
@@ -439,9 +437,7 @@ class Location(StixDomain):
439
437
  raise ValueError("If longitude is present, latitude MUST be present")
440
438
 
441
439
  if self.precision and (not self.latitude or not self.longitude):
442
- raise ValueError(
443
- "If precision is present, latitude and longitude MUST be present"
444
- )
440
+ raise ValueError("If precision is present, latitude and longitude MUST be present")
445
441
 
446
442
  return self
447
443
 
@@ -479,15 +475,15 @@ class Malware(StixDomain):
479
475
  # This property is a summary property of data from sightings and other data that may or may not be available in
480
476
  # STIX. If new sightings are received that are earlier than the first seen timestamp,
481
477
  # the object may be updated to account for the new data.
482
- first_seen: datetime | None = None
478
+ first_seen: StixTimestamp | None = None
483
479
  # The time that the malware family or malware instance was last seen.
484
480
  # This property is a summary property of data from sightings and other data that may or may not be available in
485
481
  # STIX. If new sightings are received that are later than the last_seen timestamp,
486
482
  # the object may be updated to account for the new data.
487
- last_seen: datetime | None = None
483
+ last_seen: StixTimestamp | None = None
488
484
  # The operating systems that the malware family or malware instance is executable on.
489
485
  # This applies to virtualized operating systems as well as those running on bare metal.
490
- operating_system_refs: list[Identifier] | None = None
486
+ operating_system_refs: list[Annotated[Identifier, AfterValidator(identifier_of_type("software"))]] | None = None
491
487
  # The processor architectures (e.g., x86, ARM, etc.) that the malware instance or family is executable on.
492
488
  # The values for this property SHOULD come from the processor-architecture-ov open vocabulary.
493
489
  architecture_execution_envs: list[str] | None = None
@@ -499,7 +495,7 @@ class Malware(StixDomain):
499
495
  capabilities: list[str] | None = None
500
496
  # The sample_refs property specifies a list of identifiers of the SCO file or artifact objects associated with
501
497
  # this malware instance(s) or family.
502
- sample_refs: list[Identifier] | None = None
498
+ sample_refs: list[Annotated[Identifier, AfterValidator(identifier_of_type("file", "artifact"))]] | None = None
503
499
 
504
500
  @model_validator(mode="after")
505
501
  def validate_last_seen_after_first_seen(self) -> Self:
@@ -541,16 +537,13 @@ class MalwareAnalysis(StixDomain):
541
537
  # used for the dynamic analysis of the malware instance or family. If this value is not included in conjunction
542
538
  # with the operating_system_ref property, this means that the dynamic analysis may have been performed on bare
543
539
  # metal (i.e. without virtualization) or the information was redacted.
544
- # The value of this property MUST be the identifier for a SCO software object.
545
- host_vm_ref: Identifier | None = None
540
+ host_vm_ref: Annotated[Identifier, AfterValidator(identifier_of_type("software"))] | None = None
546
541
  # The operating system used for the dynamic analysis of the malware instance or family. This applies to
547
542
  # virtualized operating systems as well as those running on bare metal.
548
- # The value of this property MUST be the identifier for a SCO software object.
549
- operating_system_ref: Identifier | None = None
543
+ operating_system_ref: Annotated[Identifier, AfterValidator(identifier_of_type("software"))] | None = None
550
544
  # Any non-standard software installed on the operating system (specified through the operating-system value)
551
545
  # used for the dynamic analysis of the malware instance or family.
552
- # The value of this property MUST be the identifier for a SCO software object.
553
- installed_software_refs: list[Identifier] | None = None
546
+ installed_software_refs: list[Annotated[Identifier, AfterValidator(identifier_of_type("software"))]] | None = None
554
547
  # The named configuration of additional product configuration parameters for this analysis run. For example, when
555
548
  # a product is configured to do full depth analysis of Window™ PE files. This configuration may have a named
556
549
  # version and that named version can be captured in this property. This will ensure additional runs can be
@@ -565,11 +558,11 @@ class MalwareAnalysis(StixDomain):
565
558
  analysis_definition_version: str | None = None
566
559
  # The date and time that the malware was first submitted for scanning or analysis. This value will stay constant
567
560
  # while the scanned date can change. For example, when Malware was submitted to a virus analysis tool.
568
- submitted: datetime | None = None
561
+ submitted: StixTimestamp | None = None
569
562
  # The date and time that the malware analysis was initiated.
570
- analysis_started: datetime | None = None
563
+ analysis_started: StixTimestamp | None = None
571
564
  # The date and time that the malware analysis ended.
572
- analysis_ended: datetime | None = None
565
+ analysis_ended: StixTimestamp | None = None
573
566
  # The classification result or name assigned to the malware instance by the scanner tool.
574
567
  result_name: str | None = None
575
568
  # The classification result as determined by the scanner or tool analysis process.
@@ -583,7 +576,15 @@ class MalwareAnalysis(StixDomain):
583
576
  # Analysis objects when the Malware sample_refs property does not contain the SCO that is included in the
584
577
  # Malware Analysis sample_ref property. Note, this property can also contain a reference to an SCO which is not
585
578
  # associated with Malware (i.e., some SCO which was scanned and found to be benign.)
586
- sample_ref: Identifier | None = None
579
+ sample_ref: (
580
+ list[
581
+ Annotated[
582
+ Identifier,
583
+ AfterValidator(identifier_of_type("file", "artifact", "network-traffic")),
584
+ ]
585
+ ]
586
+ | None
587
+ ) = None
587
588
 
588
589
  @model_validator(mode="after")
589
590
  def at_least_one_of(self) -> Self:
@@ -657,15 +658,15 @@ class ObservedData(StixDomain):
657
658
 
658
659
  type: Literal["observed-data"] = "observed-data" # pyright: ignore[reportIncompatibleVariableOverride]
659
660
  # The beginning of the time window during which the data was seen.
660
- first_observed: datetime
661
+ first_observed: StixTimestamp
661
662
  # The end of the time window during which the data was seen.
662
- last_observed: datetime
663
+ last_observed: StixTimestamp
663
664
  # The number of times that each Cyber-observable object represented in the objects or object_refs property was
664
665
  # seen. If present, this MUST be an integer between 1 and 999,999,999 inclusive.
665
666
  # If the number_observed property is greater than 1, the data contained in the objects or object_refs property was
666
667
  # seen multiple times. In these cases, object creators MAY omit properties of the SCO (such as timestamps) that are
667
668
  # specific to a single instance of that observed data.
668
- number_observed: Annotated[int, Gt(1), Gt(999999999)]
669
+ number_observed: Annotated[int, Ge(1), Le(999999999)]
669
670
  # A dictionary of SCO representing the observation. The dictionary MUST contain at least one object.
670
671
  # The cyber observable content MAY include multiple objects if those objects are related as part of a single
671
672
  # observation. Multiple objects not related to each other via cyber observable Relationships MUST NOT be contained
@@ -683,11 +684,7 @@ class ObservedData(StixDomain):
683
684
  """
684
685
  The last_observed property MUST be greater than or equal to the timestamp in the first_observed property.
685
686
  """
686
- if (
687
- self.first_observed
688
- and self.last_observed
689
- and self.first_observed > self.last_observed
690
- ):
687
+ if self.first_observed and self.last_observed and self.first_observed > self.last_observed:
691
688
  raise ValueError(
692
689
  "The last_observed property MUST be greater than or equal to the the first_observed property"
693
690
  )
@@ -700,9 +697,7 @@ class ObservedData(StixDomain):
700
697
  but both MUST NOT be present at the same time.
701
698
  """
702
699
  if self.objects is not None and self.object_refs is not None:
703
- raise ValueError(
704
- "The objects and object_refs properties MUST NOT be present at the same time"
705
- )
700
+ raise ValueError("The objects and object_refs properties MUST NOT be present at the same time")
706
701
  if self.objects is None and self.object_refs is None:
707
702
  raise ValueError("Either objects or object_refs MUST be provided")
708
703
  return self
@@ -770,7 +765,7 @@ class Report(StixDomain):
770
765
  # The date that this Report object was officially published by the creator of this report.
771
766
  # The publication date (public release, legal release, etc.) may be different than the date the report was
772
767
  # created or shared internally (the date in the created property).
773
- published: datetime
768
+ published: StixTimestamp
774
769
  # Specifies the STIX Objects that are referred to by this Report.
775
770
  object_refs: list[Identifier]
776
771
 
@@ -804,12 +799,12 @@ class ThreatActor(StixDomain):
804
799
  # This property is a summary property of data from sightings and other data that may or may not be available in
805
800
  # STIX. If new sightings are received that are earlier than the first seen timestamp, the object may be updated
806
801
  # to account for the new data.
807
- first_seen: datetime | None = None
802
+ first_seen: StixTimestamp | None = None
808
803
  # The time that this Threat Actor was last seen.
809
804
  # This property is a summary property of data from sightings and other data that may or may not be available in
810
805
  # STIX. If new sightings are received that are later than the last seen timestamp, the object may be updated to
811
806
  # account for the new data
812
- last_seen: datetime | None = None
807
+ last_seen: StixTimestamp | None = None
813
808
  # A list of roles the Threat Actor plays.
814
809
  # The values for this property SHOULD come from the threat-actor-role-ov open vocabulary.
815
810
  roles: list[str] | None = None
@@ -0,0 +1,16 @@
1
+ from datetime import UTC, datetime
2
+
3
+
4
+ def ser_datetime(value: datetime) -> str:
5
+ """
6
+ The timestamp property MUST be a valid RFC 3339-formatted timestamp [RFC3339] using the format
7
+ YYYY-MM-DDTHH:mm:ss[.s+]Z where the "s+" represents 1 or more sub-second values.
8
+ The brackets denote that sub-second precision is optional, and that if no digits are provided,
9
+ the decimal place MUST NOT be present.
10
+ The timestamp MUST be represented in the UTC timezone and MUST use the "Z" designation to indicate this.
11
+ NOTE: when using precisions greater than nanoseconds there may be implications for interoperability as they may be
12
+ truncated when stored as a UNIX timestamp or floating point number due to the precision of those formats.
13
+ """
14
+ if value.microsecond == 0:
15
+ return value.astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
16
+ return value.astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
stidantic/sro.py CHANGED
@@ -1,9 +1,11 @@
1
- from typing import Literal, Self, Annotated
2
- from datetime import datetime
3
- from pydantic import Field
1
+ from typing import Annotated, Literal, Self
2
+
3
+ from pydantic import AfterValidator, Field
4
4
  from pydantic.functional_validators import model_validator
5
5
  from pydantic.types import PositiveInt
6
- from stidantic.types import StixRelationship, Identifier, StixType
6
+
7
+ from stidantic.types import Identifier, StixRelationship, StixTimestamp, StixType
8
+ from stidantic.validators import identifier_of_type
7
9
 
8
10
 
9
11
  # 5.1 Relationship
@@ -51,13 +53,13 @@ class Relationship(StixRelationship):
51
53
  # time at which relationship will be asserted to be true.
52
54
  # If it is not specified, then the earliest time at which the relationship between the objects exists
53
55
  # is not defined.
54
- start_time: datetime | None = None
56
+ start_time: StixTimestamp | None = None
55
57
  # The latest time at which the Relationship between the objects exists. If this property is a future timestamp,
56
58
  # at the time the stop_time property is defined, then this represents an estimate by the producer of the
57
59
  # intelligence of the latest time at which relationship will be asserted to be true.
58
60
  # If stop_time is not specified, then the latest time at which the relationship between
59
61
  # the objects exists is either not known, not disclosed, or has no defined stop time.
60
- stop_time: datetime | None = None
62
+ stop_time: StixTimestamp | None = None
61
63
 
62
64
  @model_validator(mode="after")
63
65
  def validate_start_stop_interval(self) -> Self:
@@ -110,9 +112,9 @@ class Sighting(StixRelationship):
110
112
  # A description that provides more details and context about the Sighting.
111
113
  description: str | None = None
112
114
  # The beginning of the time window during which the SDO referenced by the sighting_of_ref property was sighted.
113
- first_seen: datetime | None = None
115
+ first_seen: StixTimestamp | None = None
114
116
  # The end of the time window during which the SDO referenced by the sighting_of_ref property was sighted.
115
- last_seen: datetime | None = None
117
+ last_seen: StixTimestamp | None = None
116
118
  # If present, this MUST be an integer between 0 and 999,999,999 inclusive and represents the number of times the
117
119
  # SDO referenced by the sighting_of_ref property was sighted.
118
120
  # Observed Data has a similar property called number_observed, which refers to the number of times the data was
@@ -129,19 +131,20 @@ class Sighting(StixRelationship):
129
131
  # A list of ID references to the Observed Data objects that contain the raw cyber data for this Sighting.
130
132
  # For example, a Sighting of an Indicator with an IP address could include the Observed Data for the
131
133
  # network connection that the Indicator was used to detect.
132
- # This property MUST reference only Observed Data SDOs.
133
- observed_data_refs: list[Identifier] | None = None
134
+ observed_data_refs: list[Annotated[Identifier, AfterValidator(identifier_of_type("observed-data"))]] | None = None
134
135
  # A list of ID references to the Identity or Location objects describing the entities or types of entities
135
136
  # that saw the sighting.
136
137
  # Omitting the where_sighted_refs property does not imply that the sighting was seen by the object creator.
137
138
  # To indicate that the sighting was seen by the object creator, an Identity representing the object creator
138
139
  # should be listed in where_sighted_refs.
139
- # This property MUST reference only Identity or Location SDOs.
140
- where_sighted_refs: list[Identifier] | None = None
140
+ where_sighted_refs: (
141
+ list[Annotated[Identifier, AfterValidator(identifier_of_type("identity", "location"))]] | None
142
+ ) = None
141
143
  # The summary property indicates whether the Sighting should be considered summary data.
142
144
  # Summary data is an aggregation of previous Sightings reports and should not be considered primary source data.
143
145
  # Default value is false.
144
- summary: bool | None = False
146
+ # WARN: Spec says it's a string, but description describes a boolean which defaults to false...
147
+ summary: bool | str | None = None
145
148
 
146
149
  @model_validator(mode="after")
147
150
  def validate_first_last_interval(self) -> Self: