Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next Feature Release #145

Merged
merged 21 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion cyclonedx/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,78 @@ def sha1sum(filename: str) -> str:
return h.hexdigest()


class DataFlow(Enum):
"""
This is our internal representation of the dataFlowType simple type within the CycloneDX standard.

.. note::
See the CycloneDX Schema: https://cyclonedx.org/docs/1.4/xml/#type_dataFlowType
"""
INBOUND = "inbound"
OUTBOUND = "outbound"
BI_DIRECTIONAL = "bi-directional"
UNKNOWN = "unknown"


class DataClassification:
"""
This is our internal representation of the `dataClassificationType` complex type within the CycloneDX standard.

.. note::
See the CycloneDX Schema for dataClassificationType:
https://cyclonedx.org/docs/1.4/xml/#type_dataClassificationType
"""

def __init__(self, flow: DataFlow, classification: str) -> None:
if not flow and not classification:
raise NoPropertiesProvidedException(
'One of `flow` or `classification` must be supplied - neither supplied'
)

self.flow = flow
self.classification = classification

@property
def flow(self) -> DataFlow:
"""
Specifies the flow direction of the data.

Valid values are: inbound, outbound, bi-directional, and unknown.

Direction is relative to the service.

- Inbound flow states that data enters the service
- Outbound flow states that data leaves the service
- Bi-directional states that data flows both ways
- Unknown states that the direction is not known

Returns:
`DataFlow`
"""
return self._flow

@flow.setter
def flow(self, flow: DataFlow) -> None:
self._flow = flow

@property
def classification(self) -> str:
"""
Data classification tags data according to its type, sensitivity, and value if altered, stolen, or destroyed.

Returns:
`str`
"""
return self._classification

@classification.setter
def classification(self, classification: str) -> None:
self._classification = classification


class Encoding(Enum):
"""
This is out internal representation of the encoding simple type within the CycloneDX standard.
This is our internal representation of the encoding simple type within the CycloneDX standard.

.. note::
See the CycloneDX Schema: https://cyclonedx.org/docs/1.4/#type_encoding
Expand Down
111 changes: 96 additions & 15 deletions cyclonedx/model/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
# Copyright (c) OWASP Foundation. All Rights Reserved.

from datetime import datetime, timezone
from typing import List, Optional
from typing import cast, List, Optional
from uuid import uuid4, UUID

from . import ThisTool, Tool
from .component import Component
from .service import Service
from ..parser import BaseParser


Expand Down Expand Up @@ -133,7 +134,7 @@ def from_parser(parser: BaseParser) -> 'Bom':
bom.add_components(parser.get_components())
return bom

def __init__(self) -> None:
def __init__(self, components: Optional[List[Component]] = None, services: Optional[List[Service]] = None) -> None:
"""
Create a new Bom that you can manually/programmatically add data to later.

Expand All @@ -142,7 +143,8 @@ def __init__(self) -> None:
"""
self.uuid = uuid4()
self.metadata = BomMetaData()
self._components: List[Component] = []
self.components = components
madpah marked this conversation as resolved.
Show resolved Hide resolved
self.services = services

@property
def uuid(self) -> UUID:
Expand Down Expand Up @@ -176,17 +178,17 @@ def metadata(self, metadata: BomMetaData) -> None:
self._metadata = metadata

@property
def components(self) -> List[Component]:
def components(self) -> Optional[List[Component]]:
"""
Get all the Components currently in this Bom.

Returns:
List of all Components in this Bom.
List of all Components in this Bom or `None`
"""
return self._components

@components.setter
def components(self, components: List[Component]) -> None:
def components(self, components: Optional[List[Component]]) -> None:
self._components = components

def add_component(self, component: Component) -> None:
Expand All @@ -200,8 +202,10 @@ def add_component(self, component: Component) -> None:
Returns:
None
"""
if not self.has_component(component=component):
self._components.append(component)
if not self.components:
self.components = [component]
elif not self.has_component(component=component):
self.components.append(component)

def add_components(self, components: List[Component]) -> None:
"""
Expand All @@ -214,7 +218,7 @@ def add_components(self, components: List[Component]) -> None:
Returns:
None
"""
self.components = self._components + components
self.components = (self._components or []) + components

def component_count(self) -> int:
"""
Expand All @@ -223,7 +227,7 @@ def component_count(self) -> int:
Returns:
The number of Components in this Bom as `int`.
"""
return len(self._components)
return len(self._components) if self._components else 0

def get_component_by_purl(self, purl: Optional[str]) -> Optional[Component]:
"""
Expand All @@ -236,8 +240,11 @@ def get_component_by_purl(self, purl: Optional[str]) -> Optional[Component]:
Returns:
`Component` or `None`
"""
if not self._components:
return None

if purl:
found = list(filter(lambda x: x.purl == purl, self.components))
found = list(filter(lambda x: x.purl == purl, cast(List[Component], self.components)))
if len(found) == 1:
return found[0]

Expand All @@ -263,7 +270,80 @@ def has_component(self, component: Component) -> bool:
Returns:
`bool` - `True` if the supplied Component is part of this Bom, `False` otherwise.
"""
return component in self._components
if not self.components:
return False
return component in self.components

@property
def services(self) -> Optional[List[Service]]:
"""
Get all the Services currently in this Bom.

Returns:
List of `Service` in this Bom or `None`
"""
return self._services

@services.setter
def services(self, services: Optional[List[Service]]) -> None:
self._services = services

def add_service(self, service: Service) -> None:
"""
Add a Service to this Bom instance.

Args:
service:
`cyclonedx.model.service.Service` instance to add to this Bom.

Returns:
None
"""
if not self.services:
self.services = [service]
elif not self.has_service(service=service):
self.services.append(service)

def add_services(self, services: List[Service]) -> None:
"""
Add multiple Services at once to this Bom instance.

Args:
services:
List of `cyclonedx.model.service.Service` instances to add to this Bom.

Returns:
None
"""
self.services = (self.services or []) + services

def has_service(self, service: Service) -> bool:
"""
Check whether this Bom contains the provided Service.

Args:
service:
The instance of `cyclonedx.model.service.Service` to check if this Bom contains.

Returns:
`bool` - `True` if the supplied Service is part of this Bom, `False` otherwise.
"""
if not self.services:
return False

return service in self.services

def service_count(self) -> int:
"""
Returns the current count of Services within this Bom.

Returns:
The number of Services in this Bom as `int`.
"""
if not self.services:
return 0

return len(self.services)

def has_vulnerabilities(self) -> bool:
"""
Expand All @@ -273,8 +353,9 @@ def has_vulnerabilities(self) -> bool:
`bool` - `True` if at least one `cyclonedx.model.component.Component` has at least one Vulnerability,
`False` otherwise.
"""
for c in self.components:
if c.has_vulnerabilities():
return True
if self.components:
for c in self.components:
if c.has_vulnerabilities():
return True

return False
2 changes: 1 addition & 1 deletion cyclonedx/model/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class IssueClassification(Enum):
This is out internal representation of the enum `issueClassification`.

.. note::
See the CycloneDX Schema definition: hhttps://cyclonedx.org/docs/1.4/xml/#type_issueClassification
See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_issueClassification
"""
DEFECT = 'defect'
ENHANCEMENT = 'enhancement'
Expand Down
Loading