-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add: Add models for implementing the GitHub secret scanning API
Add new models and enums required to implement the secret scanning API of GitHub.
- Loading branch information
1 parent
cf5b3ff
commit 81e91d2
Showing
2 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
# SPDX-FileCopyrightText: 2023 Greenbone AG | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from enum import Enum | ||
from typing import Optional, Union | ||
|
||
from pontos.github.models.base import GitHubModel, User | ||
from pontos.github.models.organization import Repository | ||
|
||
|
||
class AlertSort(Enum): | ||
""" | ||
The property by which to sort the alerts | ||
""" | ||
|
||
CREATED = "created" | ||
UPDATED = "updated" | ||
|
||
|
||
class AlertState(Enum): | ||
""" | ||
State of the GitHub Secrets Scanning Alert | ||
""" | ||
|
||
OPEN = "open" | ||
RESOLVED = "resolved" | ||
|
||
|
||
class Resolution(Enum): | ||
""" | ||
The reason for resolving the alert | ||
""" | ||
|
||
FALSE_POSITIVE = "false_positive" | ||
WONT_FIX = "wont_fix" | ||
REVOKED = "revoked" | ||
USED_IN_TESTS = "used_in_tests" | ||
|
||
|
||
class LocationType(Enum): | ||
""" | ||
Type of location | ||
""" | ||
|
||
COMMIT = "commit" | ||
ISSUE_TITLE = "issue_title" | ||
ISSUE_BODY = "issue_body" | ||
ISSUE_COMMENT = "issue_comment" | ||
|
||
|
||
@dataclass | ||
class SecretScanningAlert(GitHubModel): | ||
""" | ||
A GitHub Secret Scanning Alert | ||
Attributes: | ||
number: The security alert number | ||
url: The REST API URL of the alert resource | ||
html_url: The GitHub URL of the alert resource | ||
locations_url: The REST API URL of the code locations for this alert | ||
state: Sets the state of the secret scanning alert. A `resolution` must | ||
be provided when the state is set to `resolved`. | ||
created_at: The time that the alert was created | ||
updated_at: The time that the alert was last updated | ||
resolution: Required when the `state` is `resolved` | ||
resolved_at: The time that the alert was resolved | ||
resolved_by: A GitHub user who resolved the alert | ||
secret_type: The type of secret that secret scanning detected | ||
secret_type_display_name: User-friendly name for the detected secret | ||
secret: The secret that was detected | ||
repository: The GitHub repository containing the alert. It's not | ||
returned when requesting a specific alert | ||
push_protection_bypassed: Whether push protection was bypassed for the | ||
detected secret | ||
push_protection_bypassed_by: A GitHub user who bypassed the push | ||
protection | ||
push_protection_bypassed_at: The time that push protection was bypassed | ||
resolution_comment: The comment that was optionally added when this | ||
alert was closed | ||
""" | ||
|
||
number: int | ||
url: str | ||
html_url: str | ||
locations_url: str | ||
state: AlertState | ||
secret_type: str | ||
secret_type_display_name: str | ||
secret: str | ||
created_at: datetime | ||
repository: Optional[Repository] = None | ||
updated_at: Optional[datetime] = None | ||
resolution: Optional[Resolution] = None | ||
resolved_at: Optional[datetime] = None | ||
resolved_by: Optional[User] = None | ||
push_protection_bypassed: Optional[bool] = None | ||
push_protection_bypassed_by: Optional[User] = None | ||
push_protection_bypassed_at: Optional[datetime] = None | ||
resolution_comment: Optional[str] = None | ||
|
||
|
||
@dataclass | ||
class CommitLocation(GitHubModel): | ||
""" | ||
Represents a 'commit' secret scanning location type | ||
Attributes: | ||
path: The file path in the repository | ||
start_line: Line number at which the secret starts in the file | ||
end_line: Line number at which the secret ends in the file | ||
start_column: The column at which the secret starts within the start | ||
line | ||
end_column: The column at which the secret ends within the end line | ||
blob_sha: SHA-1 hash ID of the associated blob | ||
blob_url: The API URL to get the associated blob resource | ||
commit_sha: SHA-1 hash ID of the associated commit | ||
commit_url: The API URL to get the associated commit resource | ||
""" | ||
|
||
path: str | ||
start_line: int | ||
end_line: int | ||
start_column: int | ||
end_column: int | ||
blob_sha: str | ||
blob_url: str | ||
commit_sha: str | ||
commit_url: str | ||
|
||
|
||
@dataclass | ||
class IssueTitleLocation(GitHubModel): | ||
""" | ||
Represents an 'issue_title' secret scanning location type | ||
Attributes: | ||
issue_title_url: The API URL to get the issue where the secret was | ||
detected | ||
""" | ||
|
||
issue_title_url: str | ||
|
||
|
||
@dataclass | ||
class IssueBodyLocation(GitHubModel): | ||
""" | ||
Represents an 'issue_body' secret scanning location type | ||
Attributes: | ||
issue_body_url: The API URL to get the issue where the secret was | ||
detected | ||
""" | ||
|
||
issue_body_url: str | ||
|
||
|
||
@dataclass | ||
class IssueCommentLocation(GitHubModel): | ||
""" | ||
Represents an 'issue_comment' secret scanning location type | ||
Attributes: | ||
issue_comment_url: he API URL to get the issue comment where the secret | ||
was detected | ||
""" | ||
|
||
issue_comment_url: str | ||
|
||
|
||
@dataclass | ||
class AlertLocation(GitHubModel): | ||
""" | ||
Location where the secret was detected | ||
Attributes: | ||
type: The location type | ||
details: Details about the location where the secret was detected | ||
""" | ||
|
||
type: LocationType | ||
details: Union[ | ||
CommitLocation, | ||
IssueTitleLocation, | ||
IssueBodyLocation, | ||
IssueCommentLocation, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
# SPDX-FileCopyrightText: 2023 Greenbone AG | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
# ruff: noqa:E501 | ||
|
||
import unittest | ||
from datetime import datetime, timezone | ||
|
||
from pontos.github.models.secret_scanning import ( | ||
AlertState, | ||
Resolution, | ||
SecretScanningAlert, | ||
) | ||
|
||
ALERT = { | ||
"number": 2, | ||
"created_at": "2020-11-06T18:48:51Z", | ||
"url": "https://api.github.com/repos/owner/private-repo/secret-scanning/alerts/2", | ||
"html_url": "https://github.com/owner/private-repo/security/secret-scanning/2", | ||
"locations_url": "https://api.github.com/repos/owner/private-repo/secret-scanning/alerts/2/locations", | ||
"state": "resolved", | ||
"resolution": "false_positive", | ||
"resolved_at": "2020-11-07T02:47:13Z", | ||
"resolved_by": { | ||
"login": "monalisa", | ||
"id": 2, | ||
"node_id": "MDQ6VXNlcjI=", | ||
"avatar_url": "https://alambic.github.com/avatars/u/2?", | ||
"gravatar_id": "", | ||
"url": "https://api.github.com/users/monalisa", | ||
"html_url": "https://github.com/monalisa", | ||
"followers_url": "https://api.github.com/users/monalisa/followers", | ||
"following_url": "https://api.github.com/users/monalisa/following{/other_user}", | ||
"gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", | ||
"starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", | ||
"subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", | ||
"organizations_url": "https://api.github.com/users/monalisa/orgs", | ||
"repos_url": "https://api.github.com/users/monalisa/repos", | ||
"events_url": "https://api.github.com/users/monalisa/events{/privacy}", | ||
"received_events_url": "https://api.github.com/users/monalisa/received_events", | ||
"type": "User", | ||
"site_admin": True, | ||
}, | ||
"secret_type": "adafruit_io_key", | ||
"secret_type_display_name": "Adafruit IO Key", | ||
"secret": "aio_XXXXXXXXXXXXXXXXXXXXXXXXXXXX", | ||
"repository": { | ||
"id": 1296269, | ||
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", | ||
"name": "Hello-World", | ||
"full_name": "octocat/Hello-World", | ||
"owner": { | ||
"login": "octocat", | ||
"id": 1, | ||
"node_id": "MDQ6VXNlcjE=", | ||
"avatar_url": "https://github.com/images/error/octocat_happy.gif", | ||
"gravatar_id": "", | ||
"url": "https://api.github.com/users/octocat", | ||
"html_url": "https://github.com/octocat", | ||
"followers_url": "https://api.github.com/users/octocat/followers", | ||
"following_url": "https://api.github.com/users/octocat/following{/other_user}", | ||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", | ||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", | ||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions", | ||
"organizations_url": "https://api.github.com/users/octocat/orgs", | ||
"repos_url": "https://api.github.com/users/octocat/repos", | ||
"events_url": "https://api.github.com/users/octocat/events{/privacy}", | ||
"received_events_url": "https://api.github.com/users/octocat/received_events", | ||
"type": "User", | ||
"site_admin": False, | ||
}, | ||
"private": False, | ||
"html_url": "https://github.com/octocat/Hello-World", | ||
"description": "This your first repo!", | ||
"fork": False, | ||
"url": "https://api.github.com/repos/octocat/Hello-World", | ||
"archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", | ||
"assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", | ||
"blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", | ||
"branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", | ||
"collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", | ||
"comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", | ||
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", | ||
"compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", | ||
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", | ||
"contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", | ||
"deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", | ||
"downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", | ||
"events_url": "https://api.github.com/repos/octocat/Hello-World/events", | ||
"forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", | ||
"git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", | ||
"git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", | ||
"git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", | ||
"issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", | ||
"issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", | ||
"issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", | ||
"keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", | ||
"labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", | ||
"languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", | ||
"merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", | ||
"milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", | ||
"notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", | ||
"pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", | ||
"releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", | ||
"stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", | ||
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", | ||
"subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", | ||
"subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", | ||
"tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", | ||
"teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", | ||
"trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", | ||
"hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", | ||
}, | ||
"push_protection_bypassed_by": { | ||
"login": "monalisa", | ||
"id": 2, | ||
"node_id": "MDQ6VXNlcjI=", | ||
"avatar_url": "https://alambic.github.com/avatars/u/2?", | ||
"gravatar_id": "", | ||
"url": "https://api.github.com/users/monalisa", | ||
"html_url": "https://github.com/monalisa", | ||
"followers_url": "https://api.github.com/users/monalisa/followers", | ||
"following_url": "https://api.github.com/users/monalisa/following{/other_user}", | ||
"gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", | ||
"starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", | ||
"subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", | ||
"organizations_url": "https://api.github.com/users/monalisa/orgs", | ||
"repos_url": "https://api.github.com/users/monalisa/repos", | ||
"events_url": "https://api.github.com/users/monalisa/events{/privacy}", | ||
"received_events_url": "https://api.github.com/users/monalisa/received_events", | ||
"type": "User", | ||
"site_admin": True, | ||
}, | ||
"push_protection_bypassed": True, | ||
"push_protection_bypassed_at": "2020-11-06T21:48:51Z", | ||
"resolution_comment": "Example comment", | ||
} | ||
|
||
|
||
class SecretScanningAlertTestCase(unittest.TestCase): | ||
def test_from_dict(self): | ||
alert = SecretScanningAlert.from_dict(ALERT) | ||
|
||
self.assertEqual(alert.number, 2) | ||
self.assertEqual( | ||
alert.url, | ||
"https://api.github.com/repos/owner/private-repo/secret-scanning/alerts/2", | ||
) | ||
self.assertEqual( | ||
alert.html_url, | ||
"https://github.com/owner/private-repo/security/secret-scanning/2", | ||
) | ||
self.assertEqual( | ||
alert.locations_url, | ||
"https://api.github.com/repos/owner/private-repo/secret-scanning/alerts/2/locations", | ||
) | ||
self.assertEqual(alert.state, AlertState.RESOLVED) | ||
self.assertEqual(alert.secret_type, "adafruit_io_key") | ||
self.assertEqual(alert.secret_type_display_name, "Adafruit IO Key") | ||
self.assertEqual(alert.secret, "aio_XXXXXXXXXXXXXXXXXXXXXXXXXXXX") | ||
self.assertEqual(alert.repository.id, 1296269) | ||
self.assertEqual( | ||
alert.created_at, | ||
datetime(2020, 11, 6, 18, 48, 51, tzinfo=timezone.utc), | ||
) | ||
self.assertIsNone(alert.updated_at) | ||
|
||
self.assertEqual(alert.resolution, Resolution.FALSE_POSITIVE) | ||
self.assertEqual(alert.resolution_comment, "Example comment") | ||
self.assertEqual( | ||
alert.resolved_at, | ||
datetime(2020, 11, 7, 2, 47, 13, tzinfo=timezone.utc), | ||
) | ||
self.assertEqual(alert.resolved_by.login, "monalisa") | ||
|
||
self.assertTrue(alert.push_protection_bypassed) | ||
self.assertEqual(alert.push_protection_bypassed_by.login, "monalisa") | ||
self.assertEqual( | ||
alert.push_protection_bypassed_at, | ||
datetime(2020, 11, 6, 21, 48, 51, tzinfo=timezone.utc), | ||
) |