Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tests and fix linter warnings #10

Merged
merged 16 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 44 additions & 43 deletions lib/SecurityGate/Engine/Secrets.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,62 @@ package SecurityGate::Engine::Secrets {
use Mojo::JSON;

sub new {
my ($class, $token, $repository) = @_;
my ($class, $token, $repository, $severity_limits) = @_;

my $endpoint = "https://api.github.com/repos/$repository/secret-scanning/alerts";
my $userAgent = Mojo::UserAgent -> new();
my $request = $userAgent -> get($endpoint, {Authorization => "Bearer $token"}) -> result();
my $endpoint = "https://api.github.com/repos/$repository/secret-scanning/alerts";
my $userAgent = Mojo::UserAgent -> new();
my $request = $userAgent -> get($endpoint, {Authorization => "Bearer $token"}) -> result();

if ($request -> code() == 200) {
my $data = $request -> json();
my $open_alerts = 0;
my @alert_details;
if ($request -> code() == 200) {
my $data = $request -> json();
my $open_alerts = 0;
my @alert_details;

foreach my $alert (@$data) {
if ($alert -> {state} eq "open") {
$open_alerts++;
foreach my $alert (@$data) {
if ($alert -> {state} eq "open") {
$open_alerts++;

my $locations_endpoint = "https://api.github.com/repos/$repository/secret-scanning/alerts/$alert -> {number}/locations";
my $locations_request = $userAgent -> get($locations_endpoint, {Authorization => "Bearer $token"}) -> result();
my $locations_endpoint = "https://api.github.com/repos/$repository/secret-scanning/alerts/$alert -> {number}/locations";
my $locations_request = $userAgent -> get($locations_endpoint, {Authorization => "Bearer $token"}) -> result();

if ($locations_request -> code() == 200) {
my $locations = $locations_request -> json();

push @alert_details, {
alert_number => $alert -> {number},
locations => $locations,
};
if ($locations_request -> code() == 200) {
my $locations = $locations_request -> json();

push @alert_details, {
alert_number => $alert -> {number},
locations => $locations,
};
}
}
}
}
}

if ($open_alerts > 0) {
print "[!] Total of open secret scanning alerts: $open_alerts\n";

foreach my $detail (@alert_details) {
print "[-] Alert $detail -> {alert_number} found in the following locations:\n";

foreach my $location (@{$detail -> {locations}}) {
print " File: $location -> {path}, Start line: $location -> {start_line}\n";
print "[!] Total of open secret scanning alerts: $open_alerts\n";

foreach my $detail (@alert_details) {
print "[-] Alert " . $detail -> {alert_number} . " found in the following locations:\n";

foreach my $location (@{$detail -> {locations}}) {
print " File: " . $location -> {path} . ", Start line: " . $location -> {start_line} . "\n";
}
}
}

print "[+] Secret scanning alert(s) found. Blocking pipeline.\n";
return 1;
my $threshold = $severity_limits -> {high};
if ($open_alerts > $threshold) {
print "[+] More than $threshold secret scanning alerts found. Blocking pipeline.\n";
return 1;
}

else {
print "[-] Number of secret scanning alerts ($open_alerts) is within the acceptable limit ($threshold).\n";
return 0;
}
}

else {
print "[-] No secret scanning alerts found.\n";
return 0;
print "Error: Unable to fetch secret scanning alerts. HTTP status code: " . $request -> code() . "\n";
return 1;
}
}

else {
print "Error: Unable to fetch secret scanning alerts. HTTP status code: " . $request -> code() . "\n";
return 1;
}
}
}
}

1;
1;
41 changes: 22 additions & 19 deletions lib/SecurityGate/Utils/Helper.pm
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
package SecurityGate::Utils::Helper {
use strict;
use warnings;
use warnings;

sub new {
return "
\rSecurity Gate v0.1.0
\rCore Commands
\r====================
\r\tCommand Description
\r\t------- -----------
\r\t-t, --token GitHub token
\r\t-r, --repo GitHub repository, organization/repository-name
\r\t-c, --critical Critical severity limit
\r\t-h, --high High severity limit
\r\t-m, --medium Medium severity limit
\r\t-l, --low Low severity limit
\r\t--dependency-alerts Check dependency alerts
\r\t--secret-alerts Check secret scanning alerts
\r\t--code-alerts Check code scanning alerts\n\n";
}
sub new {
return <<"EOT";

Security Gate v0.0.3
Core Commands
==============
Command Description
------- -----------
-t, --token GitHub token
-r, --repo GitHub repository
-c, --critical Critical severity limit
-h, --high High severity limit
-m, --medium Medium severity limit
-l, --low Low severity limit
--dependency-alerts Check for dependency alerts
--secret-scanning-alerts Check for secret scanning alerts
--code-scanning-alerts Check for code scanning alerts

EOT
}
}

1;
1;
4 changes: 2 additions & 2 deletions security-gate.pl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ sub main {

my %alert_checks = (
'dependency-alerts' => sub { SecurityGate::Engine::Dependencies -> new($token, $repository, \%severity_limits) },
'secret-alerts' => sub { SecurityGate::Engine::Secrets -> new($token, $repository) },
'secret-alerts' => sub { SecurityGate::Engine::Secrets -> new($token, $repository, \%severity_limits) },
'code-alerts' => sub { SecurityGate::Engine::Code -> new($token, $repository, \%severity_limits) }
);

Expand Down Expand Up @@ -66,4 +66,4 @@ sub main {

else {
exit main();
}
}
64 changes: 64 additions & 0 deletions tests/code-api-request-error.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use Test::Exception;
use Test::MockObject;
use Test::Output;
use Capture::Tiny qw(capture_stdout);

{
package Mojo::UserAgent;
use Test::MockObject;

my $mock_response;

sub new {
my $class = shift;
return Test::MockObject -> new -> mock('get', sub {
my ($self, $url, $headers) = @_;
return Test::MockObject -> new -> mock('result', sub {
return $mock_response;
});
});
}

sub set_mock_response {
my ($class, $response) = @_;
$mock_response = $response;
return $mock_response;
}
}

use lib '../lib';
use SecurityGate::Engine::Code;

subtest 'API request error' => sub {
plan tests => 2;

my $mock_response = Mojo::UserAgent -> set_mock_response(Test::MockObject -> new);
$mock_response -> set_always('code', 401);

my %severity_limits = (
critical => 0,
high => 0,
medium => 0,
low => 0
);

my $result;
my $error_message = qr/Error: \s Unable \s to \s fetch \s code \s scanning \s alerts\./x;
my $status_code = qr/\s HTTP \s status \s code: \s 401/x;
my $full_error_pattern = qr/$error_message$status_code/x;

stdout_like(
sub { $result = SecurityGate::Engine::Code -> new('test_token', 'test_repo', \%severity_limits) },
$full_error_pattern,
'Correct error message for API request failure'
);

is($result, 1, 'Returns 1 when API request fails');
};

done_testing();
57 changes: 57 additions & 0 deletions tests/dependencies-api-error-handling.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use Test::Exception;
use Test::MockObject;
use Test::Output;

{
package Mojo::UserAgent;
use Test::MockObject;

my $mock_response;

sub new {
my $class = shift;
return Test::MockObject -> new -> mock('get', sub {
my ($self, $url, $headers) = @_;
return Test::MockObject -> new -> mock('result', sub {
return $mock_response;
});
});
}

sub set_mock_response {
my ($class, $response) = @_;
$mock_response = $response;
return;
}
}

use lib '../lib';
use SecurityGate::Engine::Dependencies;

subtest 'API error handling' => sub {
plan tests => 1;

my $mock_response = Test::MockObject -> new;
Mojo::UserAgent -> set_mock_response($mock_response);
$mock_response -> set_always('code', 401);

my %severity_limits = (
critical => 0,
high => 0,
medium => 0,
low => 0
);

is(
SecurityGate::Engine::Dependencies -> new('invalid_token', 'test_repo', \%severity_limits),
1,
'Returns 1 when API request fails'
);
};

done_testing();
63 changes: 63 additions & 0 deletions tests/dependencies-severity-counting.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use Test::Exception;
use Test::MockObject;
use Test::Output;

{
package Mojo::UserAgent;
use Test::MockObject;

my $mock_response;

sub new {
my $class = shift;
return Test::MockObject -> new -> mock('get', sub {
my ($self, $url, $headers) = @_;
return Test::MockObject -> new -> mock('result', sub {
return $mock_response;
});
});
}

sub set_mock_response {
my ($class, $response) = @_;
$mock_response = $response;
return;
}
}

use lib '../lib';
use SecurityGate::Engine::Dependencies;

subtest 'Severity counting' => sub {
plan tests => 1;

my $mock_response = Test::MockObject -> new;
Mojo::UserAgent -> set_mock_response($mock_response);
$mock_response -> set_always('code', 200);
$mock_response -> set_always('json', [
{ state => 'open', security_vulnerability => { severity => 'high' } },
{ state => 'open', security_vulnerability => { severity => 'critical' } },
{ state => 'open', security_vulnerability => { severity => 'medium' } },
{ state => 'closed', security_vulnerability => { severity => 'low' } },
]);

my %severity_limits = (
critical => 0,
high => 0,
medium => 0,
low => 0
);

stdout_like(
sub { SecurityGate::Engine::Dependencies -> new('test_token', 'test_repo', \%severity_limits) },
qr/critical:\ 1.*high:\ 1.*medium:\ 1.*low:\ 0/xs,
'Severity counts are correct'
);
};

done_testing();
Loading
Loading