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

Added inventory api support and experimental features #11

Merged
merged 2 commits into from
Jan 11, 2024
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
23 changes: 20 additions & 3 deletions codeinsight_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@

from .handlers import ProjectHandler, ReportHandler
from .exceptions import CodeInsightError
from .experimental import ExperimentalHandler
from .handlers.inventory import InventoryHandler

logger = logging.getLogger(__name__)

class CodeInsightClient:
"""Client for the code insight API."""

def __init__(self,
base_url: str,
api_token: str,
timeout: int = 60,
verify_ssl: bool = True
verify_ssl: bool = True,
experimental: bool = False
):

self.base_url = base_url
self.api_url = f"{base_url}/codeinsight/api"
self.__api_token = api_token
Expand All @@ -23,6 +29,7 @@ def __init__(self,
}
self.__timeout = timeout
self.__verify_ssl = verify_ssl
self.experimental_enabled = experimental

def request(self, method, url_part: str, params: dict = None, body: any = None, data: any = None, content_type: str = None):
url = f"{self.api_url}/{url_part}"
Expand Down Expand Up @@ -57,10 +64,20 @@ def projects(self) -> ProjectHandler:
def reports(self) -> ReportHandler:
return ReportHandler(self)

@property
def inventories(self):
return InventoryHandler(self)

@property
def experimental(self) -> ExperimentalHandler:
if self.experimental_enabled == False:
raise CodeInsightError("Experimental API is not enabled for this instance")
else:
return ExperimentalHandler(self)


# Coming soon...?
def inventories(self):
raise NotImplementedError("Inventories are not yet implemented")


def vulnerabilites(self):
raise NotImplementedError
Expand Down
43 changes: 43 additions & 0 deletions codeinsight_sdk/experimental.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from .handler import Handler
from .models import ProjectInventoryItem

class ExperimentalHandler(Handler):
def __init__(self, client):
super().__init__(client)

def get(self):
# Do nothing, there is no get for this handler
pass

def get_project_vulnerabilities(self, project_id:int) -> list[ProjectInventoryItem]:
"""
Get all vulnerabilities for a project.

Args:
project_id (int): The project id.

Returns:
dict: The vulnerabilities.
"""
# First we get the inventory summary for the project with vulnerability summary
# Then we iterate over the inventory items and calling the inventory vulnerability endpoint for each item with a vulnerability
inventory = self.client.projects.get_inventory_summary(project_id, vulnerabilitySummary=True)

# Iterate over the inventory items, find which have vulnerabilities.
item: ProjectInventoryItem
vuln_items: list(ProjectInventoryItem) = []
for item in inventory:
if item.vulnerabilitySummary is None:
continue

# If the item has a vulnerability, get the vulnerability details for this item and append it
if sum(item.vulnerabilitySummary[0]['CvssV3'].values()) > 0:

vul_detail = self.client.inventories.get_inventory_vulnerabilities(item.id)
item.vulnerabilities = vul_detail
vuln_items.append(item)
else:
# If the item has no vulnerabilities, skip it
continue

return vuln_items
15 changes: 15 additions & 0 deletions codeinsight_sdk/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import abc
from typing import List

from .models import Project, ProjectInventory, ProjectInventoryItem, Report
from .exceptions import CodeInsightError

class Handler(abc.ABC):
def __init__(self, client):
self.client = client
self.cls = None

@abc.abstractmethod
def get(self):
pass

3 changes: 3 additions & 0 deletions codeinsight_sdk/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .inventory import InventoryHandler
from .project import ProjectHandler
from .report import ReportHandler
50 changes: 50 additions & 0 deletions codeinsight_sdk/handlers/inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from ..models import ProjectInventoryItem, Vulnerability
from ..handler import Handler

class InventoryHandler(Handler):
""" Handles operations related to inventories."""

def __init__(self, client):
super().__init__(client)
self.cls = ProjectInventoryItem

def get(self, inventoryId: int) -> list[ProjectInventoryItem]:
"""
Get an inventory item by id.

Args:
inventoryId (int): The inventory item id.

Returns:
ProjectInventoryItem: The inventory item.
"""
path = f"inventories/{inventoryId}"
resp = self.client.request("GET", url_part=path)
inventory = []
for inv_item in resp.json()['data']:
inventory.append(ProjectInventoryItem.from_dict(inv_item))
return inventory

def get_inventory_vulnerabilities(self, inventoryId: int,
limit: int = 25,
offset: int = 1) -> list[Vulnerability]:
"""
Get all vulnerabilities for an inventory item.

Args:
inventoryId (int): The inventory item id.

Returns:
dict: The vulnerabilities.
"""
path = f"inventories/{inventoryId}/vulnerabilities"
params = {"limit": limit, "offset": offset}
resp = self.client.request("GET", url_part=path, params=params)

# TODO - Iterate pages

inventory_vuls: list(Vulnerability) = []
for v in resp.json()['data']:
inventory_vuls.append(Vulnerability.from_dict(v))

return inventory_vuls
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
import abc
from typing import List

from codeinsight_sdk.models import Project, ProjectInventory, ProjectInventoryItem, Report
from codeinsight_sdk.exceptions import CodeInsightError
from ..models import Project, ProjectInventory, ProjectInventoryItem
from ..handler import Handler

class Handler(abc.ABC):
def __init__(self, client):
self.client = client
self.cls = None

@staticmethod
def create(client, cls):
k = cls.__name__
handlers = {"Project": ProjectHandler,
"Report": ReportHandler
}
handler = handlers.get(k)
if handler is None:
raise ValueError(f"Handler not found for class '{k}'")
return handler(client)

@abc.abstractmethod
def get(self):
pass
from ..exceptions import CodeInsightError

class ProjectHandler(Handler):
def __init__(self, client):
Expand Down Expand Up @@ -208,55 +189,3 @@ def upload_codebase(self, project_id:int,
content_type = "application/octet-stream"
resp = self.client.request("POST", url_part=path, params=params, data=code_file,content_type=content_type)
return resp.status_code

class ReportHandler(Handler):
"""
A class that handles operations related to reports.

Args:
client (Client): The client object used for making API requests.

Attributes:
cls (Report): The class representing a report.

Methods:
get(id): Retrieves a report by its ID.
all(): Retrieves all reports.

"""

def __init__(self, client):
super().__init__(client)
self.cls = Report

def get(self, id:int):
"""
Retrieves a report by its ID.

Args:
id (int): The ID of the report to retrieve.

Returns:
Report: The report object.

"""
path = f"reports/{id}"
resp = self.client.request("GET", url_part=path)
report_data = resp.json()['data']
report = self.cls.from_dict(report_data)
return report

def all(self):
"""
Retrieves all reports.

Returns:
list: A list of report objects.

"""
path = "reports"
resp = self.client.request("GET", url_part=path)
reports = []
for report_data in resp.json()['data']:
reports.append(self.cls.from_dict(report_data))
return reports
55 changes: 55 additions & 0 deletions codeinsight_sdk/handlers/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from ..models import Report
from ..handler import Handler


class ReportHandler(Handler):
"""
A class that handles operations related to reports.

Args:
client (Client): The client object used for making API requests.

Attributes:
cls (Report): The class representing a report.

Methods:
get(id): Retrieves a report by its ID.
all(): Retrieves all reports.

"""

def __init__(self, client):
super().__init__(client)
self.cls = Report

def get(self, id:int):
"""
Retrieves a report by its ID.

Args:
id (int): The ID of the report to retrieve.

Returns:
Report: The report object.

"""
path = f"reports/{id}"
resp = self.client.request("GET", url_part=path)
report_data = resp.json()['data']
report = self.cls.from_dict(report_data)
return report

def all(self):
"""
Retrieves all reports.

Returns:
list: A list of report objects.

"""
path = "reports"
resp = self.client.request("GET", url_part=path)
reports = []
for report_data in resp.json()['data']:
reports.append(self.cls.from_dict(report_data))
return reports
5 changes: 4 additions & 1 deletion codeinsight_sdk/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# pylint: disable=invalid-name
# Disable invalid name because the API uses camelCase

from dataclasses import dataclass
from dataclasses_json import DataClassJsonMixin, dataclass_json
from typing import Any, Optional, List, Dict
Expand Down Expand Up @@ -39,7 +42,7 @@ class ProjectInventoryItem(DataClassJsonMixin):
componentUrl: Optional[str] = None
componentDescription: Optional[str] = None
vulnerabilities: Optional[List[Vulnerability]] = None
vulnerabilitySummary: Optional[Dict[str, Dict]] = None
vulnerabilitySummary: Optional[List[Dict[str, Dict]]] = None
filePaths: Optional[List[str]] = None

@dataclass_json #Trying this style instead of DataClassJsonMixin
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "codeinsight_sdk"
version = "0.0.7"
version = "0.0.8"
description = "A Python client for the Revenera Code Insight"
authors = ["Zachary Karpinski <[email protected]>"]
readme = "README.md"
Expand Down
4 changes: 3 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def client(self):

def test_client(self, client):
assert client.base_url == TEST_URL

def test_client_expertimental_disabled(self, client):
assert client.experimental_enabled == False

def test_endpoint_not_found(self, client):
with requests_mock.Mocker() as m:
Expand Down Expand Up @@ -168,7 +171,6 @@ def test_get_project_inventory_summary(self,client):
"componentVersionName":"2.0"
}
]

}
"""
with requests_mock.Mocker() as m:
Expand Down
Loading
Loading