Skip to content

Commit

Permalink
feat(secret_scan): add all_result option
Browse files Browse the repository at this point in the history
  • Loading branch information
gg-mmill committed Nov 29, 2024
1 parent 2b52ce0 commit 6f89d8d
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 60 deletions.
47 changes: 20 additions & 27 deletions ggshield/verticals/secret/secret_scan_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union, cast
from typing import Dict, Iterable, List, NamedTuple, Optional, Tuple, Union, cast

from pygitguardian import GGClient
from pygitguardian.models import Detail, Match, PolicyBreak, ScanResult, SecretIncident
Expand All @@ -22,7 +22,10 @@ class IgnoreReason(str, Enum):
KNOWN_SECRET = "known_secret"
BACKEND_EXCLUDED = "backend_excluded"

def compute_ignore_reason(policy_break: PolicyBreak, secret_config: SecretConfig) -> str | None:

def compute_ignore_reason(
policy_break: PolicyBreak, secret_config: SecretConfig
) -> str | None:
"""Computes the possible ignore reason associated with a PolicyBreak"""
ignore_reason = None
if policy_break.is_excluded:
Expand All @@ -37,6 +40,7 @@ def compute_ignore_reason(policy_break: PolicyBreak, secret_config: SecretConfig
return ignore_reason


@dataclass
class Result:
"""
Return model for a scan which zips the information
Expand All @@ -50,27 +54,6 @@ class Result:
policy_breaks: List[PolicyBreak]
ignored_policy_breaks_count_by_reason: Dict[str, int]

def __init__(self, file: Scannable, scan: ScanResult):
self.filename = file.filename
self.filemode = file.filemode
self.path = file.path
self.url = file.url
self.policy_breaks = scan.policy_breaks
lines = get_lines_from_content(file.content, self.filemode)
self.enrich_matches(lines)
self.ignored_policy_breaks_count_by_reason = {}

def __eq__(self, other: Any) -> bool:
if not isinstance(other, Result):
return False
return (
self.filename == other.filename
and self.filemode == other.filemode
and self.path == other.path
and self.url == other.url
and self.policy_breaks == other.policy_breaks
)

@property
def is_on_patch(self) -> bool:
return self.filemode != Filemode.FILE
Expand All @@ -97,10 +80,13 @@ def has_policy_breaks(self) -> bool:
return len(self.policy_breaks) > 0

@classmethod
def from_scan_result(cls, file: Scannable, scan_result: ScanResult, secret_config: SecretConfig):
def from_scan_result(
cls, file: Scannable, scan_result: ScanResult, secret_config: SecretConfig
):
"""Creates a Result from a Scannable and a ScanResult.
Removes ignored policy breaks
"""

to_keep = []
ignored_policy_breaks_count_by_reason = defaultdict(lambda: 0)
for policy_break in scan_result.policy_breaks:
Expand All @@ -115,10 +101,17 @@ def from_scan_result(cls, file: Scannable, scan_result: ScanResult, secret_confi
else:
to_keep.append(policy_break)

result = Result(file, to_keep)
result.ignored_policy_breaks_count_by_reason = (
ignored_policy_breaks_count_by_reason
result = Result(
filename=file.filename,
filemode=file.filemode,
path=file.path,
url=file.url,
policy_breaks=to_keep,
ignored_policy_breaks_count_by_reason=ignored_policy_breaks_count_by_reason,
)

lines = get_lines_from_content(file.content, file.filemode)
result.enrich_matches(lines)
return result


Expand Down
6 changes: 4 additions & 2 deletions tests/unit/cmd/scan/test_prereceive.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from click.testing import CliRunner

from ggshield.__main__ import cli
from ggshield.core.config.user_config import SecretConfig
from ggshield.core.errors import ExitCode
from ggshield.core.scan import StringScannable
from ggshield.utils.git_shell import EMPTY_SHA, Filemode
Expand Down Expand Up @@ -195,13 +196,14 @@ def test_stdin_supports_gitlab_web_ui(
type="commit",
results=Results(
results=[
Result(
Result.from_scan_result(
file=StringScannable(
content=_SIMPLE_SECRET_PATCH,
url="server.conf",
filemode=Filemode.MODIFY,
),
scan=_SIMPLE_SECRET_PATCH_SCAN_RESULT,
scan_result=_SIMPLE_SECRET_PATCH_SCAN_RESULT,
secret_config=SecretConfig(),
)
],
errors=[],
Expand Down
10 changes: 6 additions & 4 deletions tests/unit/verticals/secret/output/test_json_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,14 @@ def test_ignore_known_secrets(verbose, ignore_known_secrets, secrets_types):
verbose=verbose, secret_config=secret_config
)

result: Result = Result(
result: Result = Result.from_scan_result(
StringScannable(
content=_ONE_LINE_AND_MULTILINE_PATCH_CONTENT,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=deepcopy(TWO_POLICY_BREAKS), # 2 policy breaks
scan_result=deepcopy(TWO_POLICY_BREAKS),
secret_config=SecretConfig(), # 2 policy breaks
)

all_policy_breaks = result.policy_breaks
Expand Down Expand Up @@ -531,13 +532,14 @@ def test_with_incident_details(
verbose=True, secret_config=secret_config, client=client_mock
)

result: Result = Result(
result: Result = Result.from_scan_result(
StringScannable(
content=_ONE_LINE_AND_MULTILINE_PATCH_CONTENT,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=deepcopy(TWO_POLICY_BREAKS), # 2 policy breaks
scan_result=deepcopy(TWO_POLICY_BREAKS),
secret_config=SecretConfig(), # 2 policy breaks
)

all_policy_breaks = result.policy_breaks
Expand Down
8 changes: 6 additions & 2 deletions tests/unit/verticals/secret/output/test_sarif_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ def test_sarif_output_for_flat_scan_with_secrets(
policy_break.known_secret = False
policy_break.incident_url = None

result = Result(file=scannable, scan=scan_result)
result = Result.from_scan_result(
file=scannable, scan_result=scan_result, secret_config=SecretConfig()
)
results = Results(results=[result])
scan = SecretScanCollection(id="path", type="test", results=results)

Expand Down Expand Up @@ -248,7 +250,9 @@ def test_sarif_output_for_nested_scan(init_secrets_engine_version):
scannable = next(commit.get_files())
contents.append(scannable.content)

result = Result(file=scannable, scan=scan_result)
result = Result.from_scan_result(
file=scannable, scan_result=scan_result, secret_config=SecretConfig()
)
results = Results(results=[result])
scan = SecretScanCollection(id=f"nested{idx}", type="test", results=results)
nested_scans.append(scan)
Expand Down
30 changes: 18 additions & 12 deletions tests/unit/verticals/secret/output/test_text_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,68 +41,74 @@
"result_input",
[
pytest.param(
Result(
Result.from_scan_result(
StringScannable(
content=_SIMPLE_SECRET_PATCH,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=_SIMPLE_SECRET_PATCH_SCAN_RESULT,
scan_result=_SIMPLE_SECRET_PATCH_SCAN_RESULT,
secret_config=SecretConfig(),
),
id="_SIMPLE_SECRET_PATCH_SCAN_RESULT",
),
pytest.param(
Result(
Result.from_scan_result(
StringScannable(
content=_MULTI_SECRET_ONE_LINE_PATCH,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT,
scan_result=_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT,
secret_config=SecretConfig(),
),
id="_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT",
),
pytest.param(
Result(
Result.from_scan_result(
StringScannable(
content=_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT,
scan_result=_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT,
secret_config=SecretConfig(),
),
id="_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT",
),
pytest.param(
Result(
Result.from_scan_result(
StringScannable(
content=_MULTI_SECRET_TWO_LINES_PATCH,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT,
scan_result=_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT,
secret_config=SecretConfig(),
),
id="_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT",
),
pytest.param(
Result(
Result.from_scan_result(
StringScannable(
content=_SIMPLE_SECRET_MULTILINE_PATCH,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT,
scan_result=_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT,
secret_config=SecretConfig(),
),
id="_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT",
),
pytest.param(
Result(
Result.from_scan_result(
StringScannable(
content=_ONE_LINE_AND_MULTILINE_PATCH_CONTENT,
url="leak.txt",
filemode=Filemode.NEW,
),
scan=_ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT,
scan_result=_ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT,
secret_config=SecretConfig(),
),
id="_ONE_LINE_AND_MULTILINE_PATCH_CONTENT",
),
Expand Down
37 changes: 24 additions & 13 deletions tests/unit/verticals/secret/test_scan_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,15 @@ def test_scan_2_commits_same_content(secret_scanner_mock):

secret_scanner_mock.return_value.scan.return_value = Results(
results=[
Result(
Result.from_scan_result(
commit_1_files[0],
scan=deepcopy(TWO_POLICY_BREAKS),
scan_result=deepcopy(TWO_POLICY_BREAKS),
secret_config=SecretConfig(),
),
Result(
Result.from_scan_result(
commit_2_files[0],
scan=deepcopy(TWO_POLICY_BREAKS),
scan_result=deepcopy(TWO_POLICY_BREAKS),
secret_config=SecretConfig(),
),
],
errors=[],
Expand Down Expand Up @@ -206,17 +208,20 @@ def test_scan_2_commits_file_association(secret_scanner_mock):
policy_breaks_file_2_1 = deepcopy(TWO_POLICY_BREAKS)
secret_scanner_mock.return_value.scan.return_value = Results(
results=[
Result(
Result.from_scan_result(
file1_3,
scan=policy_breaks_file_1_3,
scan_result=policy_breaks_file_1_3,
secret_config=SecretConfig(),
),
Result(
Result.from_scan_result(
file2_1,
scan=policy_breaks_file_2_1,
scan_result=policy_breaks_file_2_1,
secret_config=SecretConfig(),
),
Result(
Result.from_scan_result(
file1_1,
scan=policy_breaks_file_1_1,
scan_result=policy_breaks_file_1_1,
secret_config=SecretConfig(),
),
],
errors=[],
Expand All @@ -239,13 +244,19 @@ def test_scan_2_commits_file_association(secret_scanner_mock):
scan_collection.scans[0].results.results, key=lambda f: f.filename
) == sorted(
[
Result(file1_3, policy_breaks_file_1_3),
Result(file1_1, policy_breaks_file_1_1),
Result.from_scan_result(
file1_3, policy_breaks_file_1_3, secret_config=SecretConfig()
),
Result.from_scan_result(
file1_1, policy_breaks_file_1_1, secret_config=SecretConfig()
),
],
key=lambda f: f.filename,
)

# out of 2 files, only 1 result was returned
assert scan_collection.scans[1].results.results == [
Result(file2_1, policy_breaks_file_2_1),
Result.from_scan_result(
file2_1, policy_breaks_file_2_1, secret_config=SecretConfig()
),
]

0 comments on commit 6f89d8d

Please sign in to comment.