Skip to content

Commit

Permalink
Merge pull request #111 from GitGuardian/garancegourdel/scrt-4626-ggs…
Browse files Browse the repository at this point in the history
…hield-display-custom-remediation-message-in-ggshield-if

Add remediation messages to GGclient
  • Loading branch information
fnareoh authored Jul 24, 2024
2 parents a99ced7 + 6b57cef commit fc766e1
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- GGClient now contains remediation messages obtained from the API `/metadata` endpoint.
4 changes: 4 additions & 0 deletions pygitguardian/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
JWTService,
MultiScanResult,
QuotaResponse,
RemediationMessages,
ScanResult,
SecretScanPreferences,
ServerMetadata,
Expand Down Expand Up @@ -151,6 +152,7 @@ class GGClient:
user_agent: str
extra_headers: Dict
secret_scan_preferences: SecretScanPreferences
remediation_messages: RemediationMessages
callbacks: Optional[GGClientCallbacks]

def __init__(
Expand Down Expand Up @@ -214,6 +216,7 @@ def __init__(
)
self.maximum_payload_size = MAXIMUM_PAYLOAD_SIZE
self.secret_scan_preferences = SecretScanPreferences()
self.remediation_messages = RemediationMessages()

def request(
self,
Expand Down Expand Up @@ -676,6 +679,7 @@ def read_metadata(self) -> Optional[Detail]:
"general__maximum_payload_size", MAXIMUM_PAYLOAD_SIZE
)
self.secret_scan_preferences = metadata.secret_scan_preferences
self.remediation_messages = metadata.remediation_messages
return None

def create_jwt(
Expand Down
48 changes: 48 additions & 0 deletions pygitguardian/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,51 @@
MULTI_DOCUMENT_LIMIT = 20
DOCUMENT_SIZE_THRESHOLD_BYTES = 1048576 # 1MB
MAXIMUM_PAYLOAD_SIZE = 2621440 # 25MB


DEFAULT_REWRITE_GIT_HISTORY_MESSAGE = """
To prevent having to rewrite git history in the future, setup ggshield as a pre-commit hook:
https://docs.gitguardian.com/ggshield-docs/integrations/git-hooks/pre-commit
"""

DEFAULT_PRE_COMMIT_MESSAGE = """> How to remediate
Since the secret was detected before the commit was made:
1. replace the secret with its reference (e.g. environment variable).
2. commit again.
> [To apply with caution] If you want to bypass ggshield (false positive or other reason), run:
- if you use the pre-commit framework:
SKIP=ggshield git commit -m "<your message>"""

DEFAULT_PRE_PUSH_MESSAGE = (
"""> How to remediate
Since the secret was detected before the push BUT after the commit, you need to:
1. rewrite the git history making sure to replace the secret with its reference (e.g. environment variable).
2. push again.
"""
+ DEFAULT_REWRITE_GIT_HISTORY_MESSAGE
+ """
> [To apply with caution] If you want to bypass ggshield (false positive or other reason), run:
- if you use the pre-commit framework:
SKIP=ggshield-push git push"""
)

DEFAULT_PRE_RECEIVE_MESSAGE = (
"""> How to remediate
A pre-receive hook set server side prevented you from pushing secrets.
Since the secret was detected during the push BUT after the commit, you need to:
1. rewrite the git history making sure to replace the secret with its reference (e.g. environment variable).
2. push again.
"""
+ DEFAULT_REWRITE_GIT_HISTORY_MESSAGE
+ """
> [To apply with caution] If you want to bypass ggshield (false positive or other reason), run:
git push -o breakglass"""
)
18 changes: 17 additions & 1 deletion pygitguardian/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
)
from typing_extensions import Self

from .config import DOCUMENT_SIZE_THRESHOLD_BYTES, MULTI_DOCUMENT_LIMIT
from .config import (
DEFAULT_PRE_COMMIT_MESSAGE,
DEFAULT_PRE_PUSH_MESSAGE,
DEFAULT_PRE_RECEIVE_MESSAGE,
DOCUMENT_SIZE_THRESHOLD_BYTES,
MULTI_DOCUMENT_LIMIT,
)


class ToDictMixin:
Expand Down Expand Up @@ -734,13 +740,23 @@ class SecretScanPreferences:
maximum_documents_per_scan: int = MULTI_DOCUMENT_LIMIT


@dataclass
class RemediationMessages:
pre_commit: str = DEFAULT_PRE_COMMIT_MESSAGE
pre_push: str = DEFAULT_PRE_PUSH_MESSAGE
pre_receive: str = DEFAULT_PRE_RECEIVE_MESSAGE


@dataclass
class ServerMetadata(Base, FromDictMixin):
version: str
preferences: Dict[str, Any]
secret_scan_preferences: SecretScanPreferences = field(
default_factory=SecretScanPreferences
)
remediation_messages: RemediationMessages = field(
default_factory=RemediationMessages
)


ServerMetadata.SCHEMA = cast(
Expand Down
67 changes: 67 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
from pygitguardian.client import GGClientCallbacks, is_ok, load_detail
from pygitguardian.config import (
DEFAULT_BASE_URI,
DEFAULT_PRE_COMMIT_MESSAGE,
DEFAULT_PRE_PUSH_MESSAGE,
DEFAULT_PRE_RECEIVE_MESSAGE,
DOCUMENT_SIZE_THRESHOLD_BYTES,
MULTI_DOCUMENT_LIMIT,
)
Expand Down Expand Up @@ -1148,3 +1151,67 @@ def test_read_metadata_bad_response(client: GGClient):
assert mock_response.call_count == 1
assert detail.status_code == 500
assert detail.detail == "Failed"


METADATA_RESPONSE_NO_REMEDIATION_MESSAGES = {
"version": "dev",
"preferences": {
"general__maximum_payload_size": 26214400,
},
"secret_scan_preferences": {
"maximum_documents_per_scan": 20,
"maximum_document_size": 1048576,
},
}


@responses.activate
def test_read_metadata_no_remediation_message(client: GGClient):
"""
GIVEN a /metadata endpoint that returns a 200 status code but no remediation message
THEN a call to read_metadata() does not fail
AND remediation_message are the default ones
"""
mock_response = responses.get(
url=client._url_from_endpoint("metadata", "v1"),
body=json.dumps(METADATA_RESPONSE_NO_REMEDIATION_MESSAGES),
content_type="application/json",
)

client.read_metadata()

assert mock_response.call_count == 1
assert client.remediation_messages.pre_commit == DEFAULT_PRE_COMMIT_MESSAGE
assert client.remediation_messages.pre_push == DEFAULT_PRE_PUSH_MESSAGE
assert client.remediation_messages.pre_receive == DEFAULT_PRE_RECEIVE_MESSAGE


@responses.activate
def test_read_metadata_remediation_message(client: GGClient):
"""
GIVEN a /metadata endpoint that returns a 200 status code with a correct body with remediation message
THEN a call to read_metadata() does not fail
AND returns a valid Detail instance
"""
messages = {
"pre_commit": "message for pre-commit",
"pre_push": "message for pre-push",
"pre_receive": "message for pre-receive",
}
mock_response = responses.get(
content_type="application/json",
url=client._url_from_endpoint("metadata", "v1"),
body=json.dumps(
{
**METADATA_RESPONSE_NO_REMEDIATION_MESSAGES,
"remediation_messages": messages,
}
),
)

client.read_metadata()

assert mock_response.call_count == 1
assert client.remediation_messages.pre_commit == messages["pre_commit"]
assert client.remediation_messages.pre_push == messages["pre_push"]
assert client.remediation_messages.pre_receive == messages["pre_receive"]

0 comments on commit fc766e1

Please sign in to comment.