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

Merge branch "main" into dev #9296

Merged
merged 6 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 14 additions & 12 deletions src/GitHubVulnerabilities2Db/Collector/AdvisoryQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@
using System;
using System.Linq;
using GitHubVulnerabilities2Db.GraphQL;
using Newtonsoft.Json;

namespace GitHubVulnerabilities2Db.Collector
{
public class AdvisoryQueryBuilder : IAdvisoryQueryBuilder
{
private const string SecurityAdvisoryFields = @"databaseId
ghsaId
permalink
severity
withdrawnAt
updatedAt";

public int GetMaximumResultsPerRequest() => 100;

public string CreateSecurityAdvisoriesQuery(DateTimeOffset? updatedSince = null, string afterCursor = null)
Expand All @@ -20,27 +28,21 @@ public string CreateSecurityAdvisoriesQuery(DateTimeOffset? updatedSince = null,
edges {
cursor
node {
databaseId
permalink
severity
withdrawnAt
updatedAt
" + SecurityAdvisoryFields + @"
" + CreateVulnerabilitiesConnectionQuery() + @"
}
}
}
}";

/// <summary>
/// Source: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/queries#securityadvisory
/// </summary>
public string CreateSecurityAdvisoryQuery(SecurityAdvisory advisory)
=> @"
{
securityAdvisory(databaseId: " + advisory.DatabaseId + @") {
severity
updatedAt
identifiers {
type
value
}
securityAdvisory(ghsaId: " + JsonConvert.SerializeObject(advisory.GhsaId) + @") {
" + SecurityAdvisoryFields + @"
" + CreateVulnerabilitiesConnectionQuery(advisory.Vulnerabilities?.Edges?.Last()?.Cursor) + @"
}
}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private async Task<SecurityAdvisory> FetchAllVulnerabilitiesAsync(SecurityAdviso
var lastVulnerabilitiesFetchedCount = advisory.Vulnerabilities?.Edges?.Count() ?? 0;
while (lastVulnerabilitiesFetchedCount == _queryBuilder.GetMaximumResultsPerRequest())
{
_logger.LogInformation("Fetching more vulnerabilities for advisory with database key {GitHubDatabaseKey}", advisory.DatabaseId);
_logger.LogInformation("Fetching more vulnerabilities for advisory with database key {GitHubDatabaseKey} / GHSA ID {GhsaId}", advisory.DatabaseId, advisory.GhsaId);
var queryForAdditionalVulnerabilities = _queryBuilder.CreateSecurityAdvisoryQuery(advisory);
var responseForAdditionalVulnerabilities = await _queryService.QueryAsync(queryForAdditionalVulnerabilities, token);
var advisoryWithAdditionalVulnerabilities = responseForAdditionalVulnerabilities.Data.SecurityAdvisory;
Expand Down
10 changes: 10 additions & 0 deletions src/GitHubVulnerabilities2Db/GraphQL/QueryResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ namespace GitHubVulnerabilities2Db.GraphQL
public class QueryResponse
{
public QueryResponseData Data { get; set; }
public List<QueryError> Errors { get; set; }
}

/// <summary>
/// The optional error details returned by the GraphQL endpoint.
/// See: https://www.apollographql.com/docs/react/data/error-handling/#graphql-errors
/// </summary>
public class QueryError
{
public string Message { get; set; }
}

/// <summary>
Expand Down
21 changes: 19 additions & 2 deletions src/GitHubVulnerabilities2Db/GraphQL/QueryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,32 @@ public async Task<QueryResponse> QueryAsync(string query, CancellationToken toke
};

var response = await MakeWebRequestAsync(queryJObject.ToString(), token);
return JsonConvert.DeserializeObject<QueryResponse>(response);
var queryResponse = JsonConvert.DeserializeObject<QueryResponse>(response);

if (queryResponse.Errors != null && queryResponse.Errors.Count > 0)
{
throw new InvalidOperationException(
"The GitHub GraphQL response returned errors in the response JSON. " +
$"Response body:{Environment.NewLine}{response}");
}

return queryResponse;
}

private async Task<string> MakeWebRequestAsync(string query, CancellationToken token)
{
using (var request = CreateRequest(query))
using (var response = await _client.SendAsync(request, token))
{
return await response.Content.ReadAsStringAsync();
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new InvalidOperationException(
$"The GitHub GraphQL response returned status code {(int)response.StatusCode} {response.ReasonPhrase}. " +
$"Response body:{Environment.NewLine}{responseBody}");
}

return responseBody;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/GitHubVulnerabilities2Db/GraphQL/SecurityAdvisory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace GitHubVulnerabilities2Db.GraphQL
public class SecurityAdvisory : INode
{
public int DatabaseId { get; set; }
public string GhsaId { get; set; }
public string Permalink { get; set; }
public string Severity { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
Expand Down
3 changes: 2 additions & 1 deletion src/GitHubVulnerabilities2Db/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public class Job : JsonConfigurationJob, IDisposable
public override async Task Run()
{
var collector = _serviceProvider.GetRequiredService<IAdvisoryCollector>();
while (await collector.ProcessAsync(CancellationToken.None)) ;
while (await collector.ProcessAsync(CancellationToken.None));

}

protected override void ConfigureJobServices(IServiceCollection services, IConfigurationRoot configurationRoot)
Expand Down
52 changes: 52 additions & 0 deletions tests/GitHubVulnerabilities2Db.Facts/QueryServiceFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
Expand Down Expand Up @@ -56,6 +58,56 @@ public async Task Success()
Assert.True(_handler.WasCalled);
}

[Fact]
public async Task ErrorStatusCodeIsRejected()
{
// Arrange
var query = "someString";
_handler.ExpectedQueryContent = (new JObject { ["query"] = query }).ToString();

_handler.ExpectedEndpoint = new Uri("https://graphQL.net");
_configuration.GitHubGraphQLQueryEndpoint = _handler.ExpectedEndpoint;

_handler.ExpectedApiKey = "patpatpat";
_configuration.GitHubPersonalAccessToken = _handler.ExpectedApiKey;

var response = new QueryResponse();
_handler.ResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(JsonConvert.SerializeObject(response))
};

// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _service.QueryAsync(query, new CancellationToken()));
Assert.True(_handler.WasCalled);
Assert.Equal("The GitHub GraphQL response returned status code 400 Bad Request. Response body:\r\n{\"Data\":null,\"Errors\":null}", ex.Message);
}

[Fact]
public async Task ErrorResponseJsonIsRejected()
{
// Arrange
var query = "someString";
_handler.ExpectedQueryContent = (new JObject { ["query"] = query }).ToString();

_handler.ExpectedEndpoint = new Uri("https://graphQL.net");
_configuration.GitHubGraphQLQueryEndpoint = _handler.ExpectedEndpoint;

_handler.ExpectedApiKey = "patpatpat";
_configuration.GitHubPersonalAccessToken = _handler.ExpectedApiKey;

var response = new QueryResponse { Errors = new List<QueryError> { new QueryError { Message = "Query = not great" } } };
_handler.ResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(response))
};

// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _service.QueryAsync(query, new CancellationToken()));
Assert.True(_handler.WasCalled);
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);
}

private class QueryServiceHttpClientHandler : HttpClientHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
Expand Down