Skip to content

Commit 60a2f80

Browse files
committed
Merge branch 'main' into dev
2 parents a3ccd39 + 61e0169 commit 60a2f80

7 files changed

Lines changed: 99 additions & 16 deletions

File tree

src/GitHubVulnerabilities2Db/Collector/AdvisoryQueryBuilder.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
using System;
55
using System.Linq;
66
using GitHubVulnerabilities2Db.GraphQL;
7+
using Newtonsoft.Json;
78

89
namespace GitHubVulnerabilities2Db.Collector
910
{
1011
public class AdvisoryQueryBuilder : IAdvisoryQueryBuilder
1112
{
13+
private const string SecurityAdvisoryFields = @"databaseId
14+
ghsaId
15+
permalink
16+
severity
17+
withdrawnAt
18+
updatedAt";
19+
1220
public int GetMaximumResultsPerRequest() => 100;
1321

1422
public string CreateSecurityAdvisoriesQuery(DateTimeOffset? updatedSince = null, string afterCursor = null)
@@ -20,27 +28,21 @@ public string CreateSecurityAdvisoriesQuery(DateTimeOffset? updatedSince = null,
2028
edges {
2129
cursor
2230
node {
23-
databaseId
24-
permalink
25-
severity
26-
withdrawnAt
27-
updatedAt
31+
" + SecurityAdvisoryFields + @"
2832
" + CreateVulnerabilitiesConnectionQuery() + @"
2933
}
3034
}
3135
}
3236
}";
3337

38+
/// <summary>
39+
/// Source: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/queries#securityadvisory
40+
/// </summary>
3441
public string CreateSecurityAdvisoryQuery(SecurityAdvisory advisory)
3542
=> @"
3643
{
37-
securityAdvisory(databaseId: " + advisory.DatabaseId + @") {
38-
severity
39-
updatedAt
40-
identifiers {
41-
type
42-
value
43-
}
44+
securityAdvisory(ghsaId: " + JsonConvert.SerializeObject(advisory.GhsaId) + @") {
45+
" + SecurityAdvisoryFields + @"
4446
" + CreateVulnerabilitiesConnectionQuery(advisory.Vulnerabilities?.Edges?.Last()?.Cursor) + @"
4547
}
4648
}";

src/GitHubVulnerabilities2Db/Collector/AdvisoryQueryService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private async Task<SecurityAdvisory> FetchAllVulnerabilitiesAsync(SecurityAdviso
5353
var lastVulnerabilitiesFetchedCount = advisory.Vulnerabilities?.Edges?.Count() ?? 0;
5454
while (lastVulnerabilitiesFetchedCount == _queryBuilder.GetMaximumResultsPerRequest())
5555
{
56-
_logger.LogInformation("Fetching more vulnerabilities for advisory with database key {GitHubDatabaseKey}", advisory.DatabaseId);
56+
_logger.LogInformation("Fetching more vulnerabilities for advisory with database key {GitHubDatabaseKey} / GHSA ID {GhsaId}", advisory.DatabaseId, advisory.GhsaId);
5757
var queryForAdditionalVulnerabilities = _queryBuilder.CreateSecurityAdvisoryQuery(advisory);
5858
var responseForAdditionalVulnerabilities = await _queryService.QueryAsync(queryForAdditionalVulnerabilities, token);
5959
var advisoryWithAdditionalVulnerabilities = responseForAdditionalVulnerabilities.Data.SecurityAdvisory;

src/GitHubVulnerabilities2Db/GraphQL/QueryResponse.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ namespace GitHubVulnerabilities2Db.GraphQL
1111
public class QueryResponse
1212
{
1313
public QueryResponseData Data { get; set; }
14+
public List<QueryError> Errors { get; set; }
15+
}
16+
17+
/// <summary>
18+
/// The optional error details returned by the GraphQL endpoint.
19+
/// See: https://www.apollographql.com/docs/react/data/error-handling/#graphql-errors
20+
/// </summary>
21+
public class QueryError
22+
{
23+
public string Message { get; set; }
1424
}
1525

1626
/// <summary>

src/GitHubVulnerabilities2Db/GraphQL/QueryService.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,32 @@ public async Task<QueryResponse> QueryAsync(string query, CancellationToken toke
3939
};
4040

4141
var response = await MakeWebRequestAsync(queryJObject.ToString(), token);
42-
return JsonConvert.DeserializeObject<QueryResponse>(response);
42+
var queryResponse = JsonConvert.DeserializeObject<QueryResponse>(response);
43+
44+
if (queryResponse.Errors != null && queryResponse.Errors.Count > 0)
45+
{
46+
throw new InvalidOperationException(
47+
"The GitHub GraphQL response returned errors in the response JSON. " +
48+
$"Response body:{Environment.NewLine}{response}");
49+
}
50+
51+
return queryResponse;
4352
}
4453

4554
private async Task<string> MakeWebRequestAsync(string query, CancellationToken token)
4655
{
4756
using (var request = CreateRequest(query))
4857
using (var response = await _client.SendAsync(request, token))
4958
{
50-
return await response.Content.ReadAsStringAsync();
59+
var responseBody = await response.Content.ReadAsStringAsync();
60+
if (!response.IsSuccessStatusCode)
61+
{
62+
throw new InvalidOperationException(
63+
$"The GitHub GraphQL response returned status code {(int)response.StatusCode} {response.ReasonPhrase}. " +
64+
$"Response body:{Environment.NewLine}{responseBody}");
65+
}
66+
67+
return responseBody;
5168
}
5269
}
5370

src/GitHubVulnerabilities2Db/GraphQL/SecurityAdvisory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace GitHubVulnerabilities2Db.GraphQL
1111
public class SecurityAdvisory : INode
1212
{
1313
public int DatabaseId { get; set; }
14+
public string GhsaId { get; set; }
1415
public string Permalink { get; set; }
1516
public string Severity { get; set; }
1617
public DateTimeOffset UpdatedAt { get; set; }

src/GitHubVulnerabilities2Db/Job.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public class Job : JsonConfigurationJob, IDisposable
3434
public override async Task Run()
3535
{
3636
var collector = _serviceProvider.GetRequiredService<IAdvisoryCollector>();
37-
while (await collector.ProcessAsync(CancellationToken.None)) ;
37+
while (await collector.ProcessAsync(CancellationToken.None));
38+
3839
}
3940

4041
protected override void ConfigureJobServices(IServiceCollection services, IConfigurationRoot configurationRoot)

tests/GitHubVulnerabilities2Db.Facts/QueryServiceFacts.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Net;
57
using System.Net.Http;
68
using System.Net.Http.Headers;
79
using System.Text;
@@ -56,6 +58,56 @@ public async Task Success()
5658
Assert.True(_handler.WasCalled);
5759
}
5860

61+
[Fact]
62+
public async Task ErrorStatusCodeIsRejected()
63+
{
64+
// Arrange
65+
var query = "someString";
66+
_handler.ExpectedQueryContent = (new JObject { ["query"] = query }).ToString();
67+
68+
_handler.ExpectedEndpoint = new Uri("https://graphQL.net");
69+
_configuration.GitHubGraphQLQueryEndpoint = _handler.ExpectedEndpoint;
70+
71+
_handler.ExpectedApiKey = "patpatpat";
72+
_configuration.GitHubPersonalAccessToken = _handler.ExpectedApiKey;
73+
74+
var response = new QueryResponse();
75+
_handler.ResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest)
76+
{
77+
Content = new StringContent(JsonConvert.SerializeObject(response))
78+
};
79+
80+
// Act & Assert
81+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _service.QueryAsync(query, new CancellationToken()));
82+
Assert.True(_handler.WasCalled);
83+
Assert.Equal("The GitHub GraphQL response returned status code 400 Bad Request. Response body:\r\n{\"Data\":null,\"Errors\":null}", ex.Message);
84+
}
85+
86+
[Fact]
87+
public async Task ErrorResponseJsonIsRejected()
88+
{
89+
// Arrange
90+
var query = "someString";
91+
_handler.ExpectedQueryContent = (new JObject { ["query"] = query }).ToString();
92+
93+
_handler.ExpectedEndpoint = new Uri("https://graphQL.net");
94+
_configuration.GitHubGraphQLQueryEndpoint = _handler.ExpectedEndpoint;
95+
96+
_handler.ExpectedApiKey = "patpatpat";
97+
_configuration.GitHubPersonalAccessToken = _handler.ExpectedApiKey;
98+
99+
var response = new QueryResponse { Errors = new List<QueryError> { new QueryError { Message = "Query = not great" } } };
100+
_handler.ResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
101+
{
102+
Content = new StringContent(JsonConvert.SerializeObject(response))
103+
};
104+
105+
// Act & Assert
106+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _service.QueryAsync(query, new CancellationToken()));
107+
Assert.True(_handler.WasCalled);
108+
Assert.Equal("The GitHub GraphQL response returned errors in the response JSON. Response body:\r\n{\"Data\":null,\"Errors\":[{\"Message\":\"Query = not great\"}]}", ex.Message);
109+
}
110+
59111
private class QueryServiceHttpClientHandler : HttpClientHandler
60112
{
61113
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)