Skip to content

Commit

Permalink
Add: Add models for implementing the GitHub secret scanning API
Browse files Browse the repository at this point in the history
Add new models and enums required to implement the secret scanning API
of GitHub.
  • Loading branch information
bjoernricks committed Oct 4, 2023
1 parent cf5b3ff commit 2b12d4b
Show file tree
Hide file tree
Showing 2 changed files with 371 additions and 0 deletions.
189 changes: 189 additions & 0 deletions pontos/github/models/secret_scanning.py
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,
]
182 changes: 182 additions & 0 deletions tests/github/models/test_secret_scanning.py
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),
)

0 comments on commit 2b12d4b

Please sign in to comment.