From 7902351d4fa501dc108dbf3724e93831d922f582 Mon Sep 17 00:00:00 2001 From: Eric Nieuwland Date: Wed, 9 Nov 2022 07:59:34 +0100 Subject: [PATCH] Add code scanning alerts (#2227) --- doc/examples/Repository.rst | 25 +++ github/CodeScanAlert.py | 191 ++++++++++++++++++ github/CodeScanAlert.pyi | 62 ++++++ github/CodeScanAlertInstance.py | 125 ++++++++++++ github/CodeScanAlertInstance.pyi | 49 +++++ github/CodeScanAlertInstanceLocation.py | 98 +++++++++ github/CodeScanAlertInstanceLocation.pyi | 41 ++++ github/CodeScanRule.py | 89 ++++++++ github/CodeScanRule.pyi | 40 ++++ github/CodeScanTool.py | 73 +++++++ github/CodeScanTool.pyi | 36 ++++ github/Repository.py | 14 ++ .../Repository.testCodeScanAlerts.txt | 22 ++ tests/Repository.py | 117 +++++++++++ 14 files changed, 982 insertions(+) create mode 100644 github/CodeScanAlert.py create mode 100644 github/CodeScanAlert.pyi create mode 100644 github/CodeScanAlertInstance.py create mode 100644 github/CodeScanAlertInstance.pyi create mode 100644 github/CodeScanAlertInstanceLocation.py create mode 100644 github/CodeScanAlertInstanceLocation.pyi create mode 100644 github/CodeScanRule.py create mode 100644 github/CodeScanRule.pyi create mode 100644 github/CodeScanTool.py create mode 100644 github/CodeScanTool.pyi create mode 100644 tests/ReplayData/Repository.testCodeScanAlerts.txt diff --git a/doc/examples/Repository.rst b/doc/examples/Repository.rst index a265350e..9d3ef6a9 100644 --- a/doc/examples/Repository.rst +++ b/doc/examples/Repository.rst @@ -35,6 +35,31 @@ Get list of open issues Issue(title="Is suspended_users for github enterprise implemented in NamedUser?", number=900) Issue(title="Adding migration api wrapper", number=899) +Get list of code scanning alerts +-------------------------------- + +.. code-block:: python + + >>> repo = g.get_repo("PyGithub/PyGithub") + >>> codescan_alerts = repo.get_codescan_alerts() + >>> for alert in codescan_alerts: + ... print(alert.number, alert.created_at, alert.dismissed_at) + ... print(" ", alert.tool.name, alert.tool.version, alert.tool.guid) + ... print(" ", alert.rule.name alert.rule.security_severity_level alert.rule.severity) + ... print(" ", alert.rule.description) + ... print(" ", alert.most_recent_instance.ref, alert.most_recent_instance.state) + ... print(" ", alert.most_recent_instance.location) + ... print(" ", alert.most_recent_instance.message['text']) + ... + 3 1984-02-29 12:34:56 None + CodeQL 2.6.1 None + py/weak-sensitive-data-hashing high warning + Use of a broken or weak cryptographic hashing algorithm on sensitive data + refs/heads/master | open + src/secrets/rats.py @ l42:c13-l42:c69 + Sensitive data (password) is used in a hashing algorithm (SHA1) that is insecure⤶ + for password hashing, since it is not a computationally expensive hash function. + Get all the labels of the repository ------------------------------------ diff --git a/github/CodeScanAlert.py b/github/CodeScanAlert.py new file mode 100644 index 00000000..da386336 --- /dev/null +++ b/github/CodeScanAlert.py @@ -0,0 +1,191 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.CodeScanAlertInstance +import github.CodeScanRule +import github.CodeScanTool +import github.GithubObject +import github.NamedUser +import github.PaginatedList + + +class CodeScanAlert(github.GithubObject.NonCompletableGithubObject): + """ + This class represents alerts from code scanning. + The reference can be found here https://docs.github.com/en/rest/reference/code-scanning. + """ + + def __repr__(self): + return self.get__repr__({"number": self.number}) + + @property + def number(self): + """ + :type: int + """ + return self._number.value + + @property + def rule(self): + """ + :type: :class: `github.CodeScanRule.CodeScanRule` + """ + return self._rule.value + + @property + def tool(self): + """ + :type: :class: `github.CodeScanTool.CodeScanTool` + """ + return self._tool.value + + @property + def created_at(self): + """ + :type: datetime + """ + return self._created_at.value + + @property + def dismissed_at(self): + """ + :type: datetime + """ + return self._dismissed_at.value + + @property + def dismissed_by(self): + """ + :type: :class: `github.NamedUser.NamedUser` + """ + return self._dismissed_by.value + + @property + def dismissed_reason(self): + """ + :type: str + """ + return self._dismissed_reason.value + + @property + def url(self): + """ + :type: string + """ + return self._url.value + + @property + def html_url(self): + """ + :type: string + """ + return self._html_url.value + + @property + def instances_url(self): + """ + :type: string + """ + return self._instances_url.value + + @property + def most_recent_instance(self): + """ + :type: :class: github.CodeScanAlertInstance.CodeScanAlertInstance + """ + return self._most_recent_instance.value + + @property + def state(self): + """ + :type: str + """ + return self._state.value + + def get_instances(self): + """ + :calls: `GET` on the URL for instances as provided by Github + :rtype: :class:`github.PaginatedList.PaginatedList` of :class:`github.CodeScanAlertInstance.CodeScanAlertInstance` + """ + return github.PaginatedList.PaginatedList( + github.CodeScanAlertInstance.CodeScanAlertInstance, + self._requester, + self.instances_url, + None, + ) + + def _initAttributes(self): + self._number = github.GithubObject.NotSet + self._rule = github.GithubObject.NotSet + self._tool = github.GithubObject.NotSet + + self._created_at = github.GithubObject.NotSet + self._dismissed_at = github.GithubObject.NotSet + self._dismissed_by = github.GithubObject.NotSet + self._dismissed_reason = github.GithubObject.NotSet + + self._url = github.GithubObject.NotSet + self._html_url = github.GithubObject.NotSet + self._instances_url = github.GithubObject.NotSet + + self._most_recent_instance = github.GithubObject.NotSet + self._state = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "number" in attributes: # pragma no branch + self._number = self._makeIntAttribute(attributes["number"]) + if "rule" in attributes: # pragma no branch + self._rule = self._makeClassAttribute( + github.CodeScanRule.CodeScanRule, attributes["rule"] + ) + if "tool" in attributes: # pragma no branch + self._tool = self._makeClassAttribute( + github.CodeScanTool.CodeScanTool, attributes["tool"] + ) + + if "created_at" in attributes: # pragma no branch + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "dismissed_at" in attributes: # pragma no branch + self._dismissed_at = self._makeDatetimeAttribute(attributes["dismissed_at"]) + if "dismissed_by" in attributes: # pragma no branch + self._dismissed_by = self._makeClassAttribute( + github.NamedUser.NamedUser, attributes["dismissed_by"] + ) + if "dismissed_reason" in attributes: # pragma no branch + self._dismissed_reason = self._makeStringAttribute( + attributes["dismissed_reason"] + ) + + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + if "instances_url" in attributes: # pragma no branch + self._instances_url = self._makeStringAttribute(attributes["instances_url"]) + + if "most_recent_instance" in attributes: # pragma no branch + self._most_recent_instance = self._makeClassAttribute( + github.CodeScanAlertInstance.CodeScanAlertInstance, + attributes["most_recent_instance"], + ) + if "state" in attributes: # pragma no branch + self._state = self._makeStringAttribute(attributes["state"]) diff --git a/github/CodeScanAlert.pyi b/github/CodeScanAlert.pyi new file mode 100644 index 00000000..51a42047 --- /dev/null +++ b/github/CodeScanAlert.pyi @@ -0,0 +1,62 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict +from datetime import datetime + +import github.GithubObject +import github.PaginatedList +import github.CodeScanRule +import github.CodeScanTool +import github.CodeScanAlertInstance + +class CodeScanAlert(github.GithubObject.NonCompletableGithubObject): + def __repr__(self) -> str: ... + @property + def number(self) -> int: ... + @property + def rule(self) -> github.CodeScanRule.CodeScanRule: ... + @property + def tool(self) -> github.CodeScanTool.CodeScanTool: ... + @property + def created_at(self) -> datetime: ... + @property + def dismissed_at(self) -> datetime: ... + @property + def dismissed_by(self) -> dict: ... + @property + def dismissed_reason(self) -> str: ... + @property + def url(self) -> str: ... + @property + def html_url(self) -> str: ... + @property + def instances_url(self) -> str: ... + @property + def most_recent_instance( + self, + ) -> github.CodeScanAlertInstance.CodeScanAlertInstance: ... + @property + def state(self) -> str: ... + def get_instances(self) -> github.PaginatedList.PaginatedList: ... + def _initAttributes(self) -> None: ... + def _useAttributes(self, attributes: Dict[str, Any]) -> None: ... diff --git a/github/CodeScanAlertInstance.py b/github/CodeScanAlertInstance.py new file mode 100644 index 00000000..62ce5224 --- /dev/null +++ b/github/CodeScanAlertInstance.py @@ -0,0 +1,125 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.CodeScanAlertInstanceLocation +import github.GithubObject + + +class CodeScanAlertInstance(github.GithubObject.NonCompletableGithubObject): + """ + This class represents code scanning alert instances. + The reference can be found here https://docs.github.com/en/rest/reference/code-scanning. + """ + + def __repr__(self): + return self.get__repr__({"ref": self.ref, "analysis_key": self.analysis_key}) + + @property + def ref(self): + """ + :type: str + """ + return self._ref.value + + @property + def analysis_key(self): + """ + :type: str + """ + return self._analysis_key.value + + @property + def environment(self): + """ + :type: str + """ + return self._environment.value + + @property + def state(self): + """ + :type: str + """ + return self._state.value + + @property + def commit_sha(self): + """ + :type: str + """ + return self._commit_sha.value + + @property + def message(self): + """ + :type: str + """ + return self._message.value + + @property + def location(self): + """ + :type: :class: `github.CodeScanAlertInstanceLocation.CodeScanAlertInstanceLocation` + """ + return self._location.value + + @property + def classifications(self): + """ + :type: list of str + """ + return self._classifications.value + + def _initAttributes(self): + self._ref = github.GithubObject.NotSet + self._analysis_key = github.GithubObject.NotSet + self._environment = github.GithubObject.NotSet + self._state = github.GithubObject.NotSet + self._commit_sha = github.GithubObject.NotSet + self._message = github.GithubObject.NotSet + self._location = github.GithubObject.NotSet + self._classifications = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "ref" in attributes: # pragma no branch + self._ref = self._makeStringAttribute(attributes["ref"]) + if "analysis_key" in attributes: # pragma no branch + self._analysis_key = self._makeStringAttribute(attributes["analysis_key"]) + if "environment" in attributes: # pragma no branch + self._environment = self._makeStringAttribute(attributes["environment"]) + if "state" in attributes: # pragma no branch + self._state = self._makeStringAttribute(attributes["state"]) + if "environment" in attributes: # pragma no branch + self._environment = self._makeStringAttribute(attributes["environment"]) + if "commit_sha" in attributes: # pragma no branch + self._commit_sha = self._makeStringAttribute(attributes["commit_sha"]) + if "message" in attributes: # pragma no branch + self._message = self._makeDictAttribute(attributes["message"]) + if "location" in attributes: # pragma no branch + self._location = self._makeClassAttribute( + github.CodeScanAlertInstanceLocation.CodeScanAlertInstanceLocation, + attributes["location"], + ) + if "classifications" in attributes: # pragma no branch + self._classifications = self._makeListOfStringsAttribute( + attributes["classifications"] + ) diff --git a/github/CodeScanAlertInstance.pyi b/github/CodeScanAlertInstance.pyi new file mode 100644 index 00000000..b4b7e0e5 --- /dev/null +++ b/github/CodeScanAlertInstance.pyi @@ -0,0 +1,49 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +import github.GithubObject +import github.CodeScanAlertInstanceLocation + +class CodeScanAlertInstance(github.GithubObject.NonCompletableGithubObject): + def __repr__(self) -> str: ... + @property + def ref(self) -> str: ... + @property + def analysis_key(self) -> str: ... + @property + def environment(self) -> str: ... + @property + def state(self) -> str: ... + @property + def commit_sha(self) -> str: ... + @property + def message(self) -> str: ... + @property + def location( + self, + ) -> github.CodeScanAlertInstanceLocation.CodeScanAlertInstanceLocation: ... + @property + def classifications(self) -> list[str]: ... + def _initAttributes(self) -> None: ... + def _useAttributes(self, attributes: Dict[str, Any]) -> None: ... diff --git a/github/CodeScanAlertInstanceLocation.py b/github/CodeScanAlertInstanceLocation.py new file mode 100644 index 00000000..92b6555e --- /dev/null +++ b/github/CodeScanAlertInstanceLocation.py @@ -0,0 +1,98 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.GithubObject + + +class CodeScanAlertInstanceLocation(github.GithubObject.NonCompletableGithubObject): + """ + This class represents code scanning alert instance locations. + The reference can be found here https://docs.github.com/en/rest/reference/code-scanning. + """ + + def __str__(self): + return f"{self.path} @ l{self.start_line}:c{self.start_column}-l{self.end_line}:c{self.end_column}" + + def __repr__(self): + return self.get__repr__( + { + "path": self.path, + "start_line": self.start_line, + "start_column": self.start_column, + "end_line": self.end_line, + "end_column": self.end_column, + } + ) + + @property + def path(self): + """ + :type: str + """ + return self._path.value + + @property + def start_line(self): + """ + :type: int + """ + return self._start_line.value + + @property + def start_column(self): + """ + :type: int + """ + return self._start_column.value + + @property + def end_line(self): + """ + :type: int + """ + return self._end_line.value + + @property + def end_column(self): + """ + :type: int + """ + return self._end_column.value + + def _initAttributes(self): + self._path = github.GithubObject.NotSet + self._start_line = github.GithubObject.NotSet + self._start_column = github.GithubObject.NotSet + self._end_line = github.GithubObject.NotSet + self._end_column = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "path" in attributes: # pragma no branch + self._path = self._makeStringAttribute(attributes["path"]) + if "start_line" in attributes: # pragma no branch + self._start_line = self._makeIntAttribute(attributes["start_line"]) + if "start_column" in attributes: # pragma no branch + self._start_column = self._makeIntAttribute(attributes["start_column"]) + if "end_line" in attributes: # pragma no branch + self._end_line = self._makeIntAttribute(attributes["end_line"]) + if "end_column" in attributes: # pragma no branch + self._end_column = self._makeIntAttribute(attributes["end_column"]) diff --git a/github/CodeScanAlertInstanceLocation.pyi b/github/CodeScanAlertInstanceLocation.pyi new file mode 100644 index 00000000..0d8ab5e0 --- /dev/null +++ b/github/CodeScanAlertInstanceLocation.pyi @@ -0,0 +1,41 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +import github.GithubObject + +class CodeScanAlertInstanceLocation(github.GithubObject.NonCompletableGithubObject): + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + @property + def path(self) -> str: ... + @property + def start_line(self) -> int: ... + @property + def start_column(self) -> int: ... + @property + def end_line(self) -> int: ... + @property + def end_column(self) -> int: ... + def _initAttributes(self) -> None: ... + def _useAttributes(self, attributes: Dict[str, Any]) -> None: ... diff --git a/github/CodeScanRule.py b/github/CodeScanRule.py new file mode 100644 index 00000000..9f7e3412 --- /dev/null +++ b/github/CodeScanRule.py @@ -0,0 +1,89 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.GithubObject + + +class CodeScanRule(github.GithubObject.NonCompletableGithubObject): + """ + This class represents Alerts from code scanning. + The reference can be found here https://docs.github.com/en/rest/reference/code-scanning. + """ + + def __repr__(self): + return self.get__repr__({"id": self.id, "name": self.name}) + + @property + def id(self): + """ + :type: str + """ + return self._id.value + + @property + def name(self): + """ + :type: str + """ + return self._name.value + + @property + def severity(self): + """ + :type: str + """ + return self._severity.value + + @property + def security_severity_level(self): + """ + :type: str + """ + return self._security_severity_level.value + + @property + def description(self): + """ + :type: str + """ + return self._description.value + + def _initAttributes(self): + self._id = github.GithubObject.NotSet + self._name = github.GithubObject.NotSet + self._severity = github.GithubObject.NotSet + self._security_severity_level = github.GithubObject.NotSet + self._description = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "id" in attributes: # pragma no branch + self._id = self._makeStringAttribute(attributes["id"]) + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "severity" in attributes: # pragma no branch + self._severity = self._makeStringAttribute(attributes["severity"]) + if "security_severity_level" in attributes: # pragma no branch + self._security_severity_level = self._makeStringAttribute( + attributes["security_severity_level"] + ) + if "description" in attributes: # pragma no branch + self._description = self._makeStringAttribute(attributes["description"]) diff --git a/github/CodeScanRule.pyi b/github/CodeScanRule.pyi new file mode 100644 index 00000000..c4b30feb --- /dev/null +++ b/github/CodeScanRule.pyi @@ -0,0 +1,40 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +import github.GithubObject + +class CodeScanRule(github.GithubObject.NonCompletableGithubObject): + def __repr__(self) -> str: ... + @property + def id(self) -> str: ... + @property + def name(self) -> str: ... + @property + def severity(self) -> str: ... + @property + def security_severity_level(self) -> str: ... + @property + def description(self) -> str: ... + def _initAttributes(self) -> None: ... + def _useAttributes(self, attributes: Dict[str, Any]) -> None: ... diff --git a/github/CodeScanTool.py b/github/CodeScanTool.py new file mode 100644 index 00000000..afde1a02 --- /dev/null +++ b/github/CodeScanTool.py @@ -0,0 +1,73 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.GithubObject + + +class CodeScanTool(github.GithubObject.NonCompletableGithubObject): + """ + This class represents code scanning tools. + The reference can be found here https://docs.github.com/en/rest/reference/code-scanning. + """ + + def __repr__(self): + return self.get__repr__( + { + "guid": self.guid, + "name": self.name, + "version": self.version, + } + ) + + @property + def name(self): + """ + :type: str + """ + return self._name.value + + @property + def version(self): + """ + :type: str + """ + return self._version.value + + @property + def guid(self): + """ + :type: str + """ + return self._guid.value + + def _initAttributes(self): + self._name = github.GithubObject.NotSet + self._version = github.GithubObject.NotSet + self._guid = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "name" in attributes: # pragma no branch + self._name = self._makeStringAttribute(attributes["name"]) + if "version" in attributes: # pragma no branch + self._version = self._makeStringAttribute(attributes["version"]) + if "guid" in attributes: # pragma no branch + self._guid = self._makeStringAttribute(attributes["guid"]) diff --git a/github/CodeScanTool.pyi b/github/CodeScanTool.pyi new file mode 100644 index 00000000..21a6b8dc --- /dev/null +++ b/github/CodeScanTool.pyi @@ -0,0 +1,36 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2022 Eric Nieuwland # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from typing import Any, Dict + +import github.GithubObject + +class CodeScanTool(github.GithubObject.NonCompletableGithubObject): + def __repr__(self) -> str: ... + @property + def name(self) -> str: ... + @property + def version(self) -> str: ... + @property + def guid(self) -> str: ... + def _initAttributes(self) -> None: ... + def _useAttributes(self, attributes: Dict[str, Any]) -> None: ... diff --git a/github/Repository.py b/github/Repository.py index 1090c8ad..954dd767 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -66,6 +66,7 @@ # Copyright 2018 Leying Chen # # Copyright 2020 Pascal Hofmann # # Copyright 2022 Aleksei Fedotov # +# Copyright 2022 Eric Nieuwland # # # # This file is part of PyGithub. # # http://pygithub.readthedocs.io/ # @@ -98,6 +99,7 @@ import github.Branch import github.CheckRun import github.CheckSuite import github.Clones +import github.CodeScanAlert import github.Commit import github.CommitComment import github.Comparison @@ -3772,6 +3774,18 @@ class Repository(github.GithubObject.CompletableGithubObject): return github.Artifact.Artifact(self._requester, headers, data, completed=True) + def get_codescan_alerts(self): + """ + :calls: `GET https://api.github.com/repos/{owner}/{repo}/code-scanning/alerts `_ + :rtype: :class:`github.PaginatedList.PaginatedList` of :class:`github.CodeScanAlert.CodeScanAlert` + """ + return github.PaginatedList.PaginatedList( + github.CodeScanAlert.CodeScanAlert, + self._requester, + f"{self.url}/code-scanning/alerts", + None, + ) + def _initAttributes(self): self._allow_merge_commit = github.GithubObject.NotSet self._allow_rebase_merge = github.GithubObject.NotSet diff --git a/tests/ReplayData/Repository.testCodeScanAlerts.txt b/tests/ReplayData/Repository.testCodeScanAlerts.txt new file mode 100644 index 00000000..e2923a65 --- /dev/null +++ b/tests/ReplayData/Repository.testCodeScanAlerts.txt @@ -0,0 +1,22 @@ +https +GET +api.github.com +None +/repos/jacquev6/PyGithub/code-scanning/alerts +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4963'), ('content-length', '318'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"3ce61bc2417a6a4f7b47976a7969c711"'), ('date', 'Sun, 20 May 2012 12:10:52 GMT'), ('content-type', 'application/json; charset=utf-8')] +[{"number": 6, "created_at": "2021-06-29T12:28:30Z", "dismissed_at": "2021-06-30T05:05:05Z", "dismissed_by": {"url": "dismissed_by.url", "avatar_url": "dismissed_by.avatar_url", "gravatar_id": "dismissed_by.gravatar_id", "login": "dismisser.login", "id": 42}, "dismissed_reason": "Won't tell", "state": "open", "url": "https://api.github.com/repos/jacquev6/PyGithub/code-scanning/alerts/6", "html_url": "https://github.com/jacquev6/PyGithub/security/code-scanning/6", "instances_url": "https://api.github.com/repos/jacquev6/PyGithub/code-scanning/alerts/6/instances", "most_recent_instance": {"analysis_key": ".github/workflows/codeql-analysis.yml:analyze", "ref": "refs/heads/master", "state": "open", "classifications": ["stupid typo"], "commit_sha": "deadbeef", "environment": "{language:python}", "message": {"text": "Awful stuff might happen."}, "location": {"path": "tests/ReplayData/Repository.testCodeScanAlerts.txt", "start_line": 10, "start_column": 2, "end_line": 10, "end_column": 48}}, "rule": {"id": "py/rule-id", "name": "py/rule-name", "security_severity_level": "high", "severity": "warning", "description": "Bad practice"}, "tool": {"guid": null, "name": "CodeQL", "version": "2.5.7"}}] + +https +GET +api.github.com +None +/repos/jacquev6/PyGithub/code-scanning/alerts/6/instances +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4963'), ('content-length', '318'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"3ce61bc2417a6a4f7b47976a7969c711"'), ('date', 'Sun, 20 May 2012 12:10:52 GMT'), ('content-type', 'application/json; charset=utf-8')] +[{"analysis_key": "instances[0].analysis_key", "ref": "instances[0].ref", "state": "instances[0].state", "classifications": ["instances[0].classifications"], "commit_sha": "instances[0].commit_sha", "environment": "instances[0].environment", "message": {"text": "instances[0].message"}, "location": {"path": "tests/ReplayData/Repository.testCodeScanAlerts.txt", "start_line": 10, "start_column": 2, "end_line": 10, "end_column": 48}}, {"analysis_key": "instances[1].analysis_key", "ref": "instances[1].ref", "state": "instances[1].state", "classifications": ["instances[1].classifications"], "commit_sha": "instances[1].commit_sha", "environment": "instances[1].environment", "message": {"text": "instances[1].message"}, "location": {"path": "tests/ReplayData/Repository.testCodeScanAlerts.txt", "start_line": 20, "start_column": 17, "end_line": 20, "end_column": 42}}] + diff --git a/tests/Repository.py b/tests/Repository.py index 95bf5e4f..b34a1041 100644 --- a/tests/Repository.py +++ b/tests/Repository.py @@ -457,6 +457,123 @@ class Repository(Framework.TestCase): def testDeleteSecret(self): self.assertTrue(self.repo.delete_secret("secret_name")) + def testCodeScanAlerts(self): + codescan_alerts = self.repo.get_codescan_alerts() + self.assertListKeyEqual( + codescan_alerts, + lambda c: c.number, + [ + 6, + ], + ) + codescan_alert = codescan_alerts[0] + self.assertEqual(repr(codescan_alert), "CodeScanAlert(number=6)") + self.assertEqual(codescan_alert.state, "open") + self.assertEqual( + codescan_alert.url, + "https://api.github.com/repos/jacquev6/PyGithub/code-scanning/alerts/6", + ) + self.assertEqual( + codescan_alert.created_at, + datetime.datetime(2021, 6, 29, 12, 28, 30), + ) + self.assertEqual( + codescan_alert.dismissed_at, + datetime.datetime(2021, 6, 30, 5, 5, 5), + ) + self.assertEqual(codescan_alert.dismissed_reason, "Won't tell") + dismissed_by = codescan_alert.dismissed_by + self.assertEqual(dismissed_by.login, "dismisser.login") + instance = codescan_alert.most_recent_instance + self.assertEqual( + repr(instance), + "CodeScanAlertInstance(" + 'ref="refs/heads/master", ' + 'analysis_key=".github/workflows/codeql-analysis.yml:analyze"' + ")", + ) + self.assertEqual(instance.ref, "refs/heads/master") + self.assertEqual( + instance.analysis_key, ".github/workflows/codeql-analysis.yml:analyze" + ) + self.assertEqual(instance.environment, "{language:python}") + self.assertEqual(instance.state, "open") + self.assertListEqual(instance.classifications, ["stupid typo"]) + self.assertDictEqual(instance.message, {"text": "Awful stuff might happen."}) + self.assertEqual(instance.commit_sha, "deadbeef") + location = instance.location + self.assertEqual( + str(location), + "tests/ReplayData/Repository.testCodeScanAlerts.txt @ l10:c2-l10:c48", + ) + self.assertEqual( + repr(location), + "CodeScanAlertInstanceLocation(" + "start_line=10, start_column=2, " + 'path="tests/ReplayData/Repository.testCodeScanAlerts.txt", ' + "end_line=10, end_column=48" + ")", + ) + self.assertEqual( + location.path, "tests/ReplayData/Repository.testCodeScanAlerts.txt" + ) + self.assertEqual(location.start_line, 10) + self.assertEqual(location.start_column, 2) + self.assertEqual(location.end_line, 10) + self.assertEqual(location.end_column, 48) + rule = codescan_alert.rule + self.assertEqual( + repr(rule), 'CodeScanRule(name="py/rule-name", id="py/rule-id")' + ) + self.assertEqual(rule.id, "py/rule-id") + self.assertEqual(rule.name, "py/rule-name") + self.assertEqual(rule.security_severity_level, "high") + self.assertEqual(rule.severity, "warning") + self.assertEqual(rule.description, "Bad practice") + tool = codescan_alert.tool + self.assertEqual( + repr(tool), 'CodeScanTool(version="2.5.7", name="CodeQL", guid=None)' + ) + self.assertEqual(tool.guid, None) + self.assertEqual(tool.name, "CodeQL") + self.assertEqual(tool.version, "2.5.7") + instances = list(codescan_alert.get_instances()) + self.assertEqual(len(instances), 2) + # + instance = instances[0] + self.assertEqual(instance.ref, "instances[0].ref") + self.assertEqual(instance.analysis_key, "instances[0].analysis_key") + self.assertEqual(instance.environment, "instances[0].environment") + self.assertEqual(instance.state, "instances[0].state") + self.assertListEqual(instance.classifications, ["instances[0].classifications"]) + self.assertDictEqual(instance.message, {"text": "instances[0].message"}) + self.assertEqual(instance.commit_sha, "instances[0].commit_sha") + location = instance.location + self.assertEqual( + location.path, "tests/ReplayData/Repository.testCodeScanAlerts.txt" + ) + self.assertEqual(location.start_line, 10) + self.assertEqual(location.start_column, 2) + self.assertEqual(location.end_line, 10) + self.assertEqual(location.end_column, 48) + # + instance = instances[1] + self.assertEqual(instance.ref, "instances[1].ref") + self.assertEqual(instance.analysis_key, "instances[1].analysis_key") + self.assertEqual(instance.environment, "instances[1].environment") + self.assertEqual(instance.state, "instances[1].state") + self.assertListEqual(instance.classifications, ["instances[1].classifications"]) + self.assertDictEqual(instance.message, {"text": "instances[1].message"}) + self.assertEqual(instance.commit_sha, "instances[1].commit_sha") + location = instance.location + self.assertEqual( + location.path, "tests/ReplayData/Repository.testCodeScanAlerts.txt" + ) + self.assertEqual(location.start_line, 20) + self.assertEqual(location.start_column, 17) + self.assertEqual(location.end_line, 20) + self.assertEqual(location.end_column, 42) + def testCollaborators(self): lyloa = self.g.get_user("Lyloa") self.assertFalse(self.repo.has_in_collaborators(lyloa))