diff --git a/Annoy-o-Bot.Tests/GitHub/Callbacks/CallbackModelTests.cs b/Annoy-o-Bot.Tests/GitHub/Callbacks/CallbackModelTests.cs new file mode 100644 index 0000000..9d04dbf --- /dev/null +++ b/Annoy-o-Bot.Tests/GitHub/Callbacks/CallbackModelTests.cs @@ -0,0 +1,69 @@ +using System.IO; +using Newtonsoft.Json; +using Xunit; + +namespace Annoy_o_Bot.GitHub.Callbacks; + +public class CallbackModelTests +{ + [Fact] + public void NewFileAdded() + { + var result = JsonConvert.DeserializeObject(File.ReadAllText("requests/fileAdded.json")); + + Assert.Equal("refs/heads/master", result.Ref); + + Assert.Equal(7498230L, result.Installation.Id); + + Assert.Equal(179716425L, result.Repository.Id); + Assert.Equal("master", result.Repository.DefaultBranch); + Assert.Equal("TitleTest", result.Repository.Name); + + var commit = Assert.Single(result.Commits); + Assert.Equal("ba7c6f17f5beaafc603eca52b864356848865fec", commit.Id); + var addedFile = Assert.Single(commit.Added); + Assert.Equal(".reminder/testReminder.json", addedFile); + Assert.Equal("Create trigger4.json", commit.Message); + + Assert.Equal("timbussmann", result.Pusher.Name); + } + + [Fact] + public void MultiCommit() + { + var result = JsonConvert.DeserializeObject(File.ReadAllText("requests/multiCommitFileHistory.json")); + + Assert.Equal(4, result.Commits.Length); + + Assert.Equal(".reminder/newFile.json", Assert.Single(result.Commits[0].Added)); + Assert.Empty(result.Commits[0].Modified); + Assert.Empty(result.Commits[0].Removed); + Assert.Equal("Create newFile.json", result.Commits[0].Message); + + Assert.Equal(".reminder/newFile.json", Assert.Single(result.Commits[1].Modified)); + Assert.Empty(result.Commits[1].Added); + Assert.Empty(result.Commits[1].Removed); + Assert.Equal("Update newFile.json", result.Commits[1].Message); + + Assert.Equal(".reminder/newFile.json", Assert.Single(result.Commits[2].Removed)); + Assert.Empty(result.Commits[2].Added); + Assert.Empty(result.Commits[2].Modified); + Assert.Equal("Delete newFile.json", result.Commits[2].Message); + + Assert.Empty(result.Commits[3].Added); + Assert.Empty(result.Commits[3].Modified); + Assert.Empty(result.Commits[3].Removed); + Assert.Equal("Merge pull request #15 from timbussmann/file-ops-history\n\nCreate newFile.json", result.Commits[3].Message); + + Assert.Equal("cb1ec97f51657c2718ab4e0b1d0bf2656aeb3127", result.HeadCommit.Id); + } + + [Fact] + public void BranchDeleted() + { + var result = JsonConvert.DeserializeObject(File.ReadAllText("requests/branchDeleted.json")); + + Assert.Null(result.HeadCommit); + Assert.Empty(result.Commits); + } +} \ No newline at end of file diff --git a/Annoy-o-Bot.Tests/RequestParserTests.cs b/Annoy-o-Bot.Tests/RequestParserTests.cs deleted file mode 100644 index 96d6afb..0000000 --- a/Annoy-o-Bot.Tests/RequestParserTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.IO; -using Annoy_o_Bot.GitHub; -using Annoy_o_Bot.GitHub.Callbacks; -using Xunit; - -namespace Annoy_o_Bot.Tests -{ - public class RequestParserTests - { - [Fact] - public void NewFileAdded() - { - var result = RequestParser.ParseJson(File.ReadAllText("requests/fileAdded.json")); - - Assert.Equal("refs/heads/master", result.Ref); - - Assert.Equal(7498230L, result.Installation.Id); - - Assert.Equal(179716425L, result.Repository.Id); - Assert.Equal("master", result.Repository.DefaultBranch); - Assert.Equal("TitleTest", result.Repository.Name); - - var commit = Assert.Single(result.Commits); - Assert.Equal("ba7c6f17f5beaafc603eca52b864356848865fec", commit.Id); - var addedFile = Assert.Single(commit.Added); - Assert.Equal(".reminder/testReminder.json", addedFile); - Assert.Equal("Create trigger4.json", commit.Message); - - Assert.Equal("timbussmann", result.Pusher.Name); - } - - [Fact] - public void MultiCommit() - { - var result = RequestParser.ParseJson(File.ReadAllText("requests/multiCommitFileHistory.json")); - - Assert.Equal(4, result.Commits.Length); - - Assert.Equal(".reminder/newFile.json", Assert.Single(result.Commits[0].Added)); - Assert.Empty(result.Commits[0].Modified); - Assert.Empty(result.Commits[0].Removed); - Assert.Equal("Create newFile.json", result.Commits[0].Message); - - Assert.Equal(".reminder/newFile.json", Assert.Single(result.Commits[1].Modified)); - Assert.Empty(result.Commits[1].Added); - Assert.Empty(result.Commits[1].Removed); - Assert.Equal("Update newFile.json", result.Commits[1].Message); - - Assert.Equal(".reminder/newFile.json", Assert.Single(result.Commits[2].Removed)); - Assert.Empty(result.Commits[2].Added); - Assert.Empty(result.Commits[2].Modified); - Assert.Equal("Delete newFile.json", result.Commits[2].Message); - - Assert.Empty(result.Commits[3].Added); - Assert.Empty(result.Commits[3].Modified); - Assert.Empty(result.Commits[3].Removed); - Assert.Equal("Merge pull request #15 from timbussmann/file-ops-history\n\nCreate newFile.json", result.Commits[3].Message); - - Assert.Equal("cb1ec97f51657c2718ab4e0b1d0bf2656aeb3127", result.HeadCommit.Id); - } - - [Fact] - public void BranchDeleted() - { - var result = RequestParser.ParseJson(File.ReadAllText("requests/branchDeleted.json")); - - Assert.Null(result.HeadCommit); - Assert.Empty(result.Commits); - } - } -} \ No newline at end of file diff --git a/Annoy-o-Bot/CallbackHandler.cs b/Annoy-o-Bot/CallbackHandler.cs index 9f88f98..581a2f1 100644 --- a/Annoy-o-Bot/CallbackHandler.cs +++ b/Annoy-o-Bot/CallbackHandler.cs @@ -31,15 +31,14 @@ public async Task Run( { var cosmosWrapper = new CosmosClientWrapper(cosmosContainer); - if (!GitHubCallbackRequest.IsGitCommitCallback(req, log)) + var secret = configuration.GetValue("WebhookSecret") ?? + throw new Exception("Missing 'WebhookSecret' setting to validate GitHub callbacks."); + var commitModel = await GitHubCallbackRequest.Validate(req, secret, log); + if (commitModel == null) { return new OkResult(); } - var secret = configuration.GetValue("WebhookSecret") ?? - throw new Exception("Missing 'WebhookSecret' setting to validate GitHub callbacks."); - var commitModel = await GitHubCallbackRequest.Validate(req, secret); - if (commitModel.HeadCommit == null) { // no commits on push (e.g. branch delete) diff --git a/Annoy-o-Bot/GitHub/Callbacks/CallbackModel.cs b/Annoy-o-Bot/GitHub/Callbacks/CallbackModel.cs index 5d7f14d..b03f642 100644 --- a/Annoy-o-Bot/GitHub/Callbacks/CallbackModel.cs +++ b/Annoy-o-Bot/GitHub/Callbacks/CallbackModel.cs @@ -3,51 +3,44 @@ using System; using Newtonsoft.Json; -namespace Annoy_o_Bot.GitHub.Callbacks +namespace Annoy_o_Bot.GitHub.Callbacks; + +public class CallbackModel { - public class CallbackModel - { - public static CallbackModel FromJson(string json) - { - var requestObject = JsonConvert.DeserializeObject(json); - return requestObject; - } - - public InstallationModel Installation { get; set; } - public RepositoryModel Repository { get; set; } - public CommitModel[] Commits { get; set; } - public string Ref { get; set; } + public InstallationModel Installation { get; set; } + public RepositoryModel Repository { get; set; } + public CommitModel[] Commits { get; set; } + public string Ref { get; set; } - [JsonProperty("head_commit")] - public CommitModel HeadCommit { get; set; } - public PusherModel Pusher { get; set; } + [JsonProperty("head_commit")] + public CommitModel HeadCommit { get; set; } + public PusherModel Pusher { get; set; } - public class CommitModel - { - public string Id { get; set; } - public string Message { get; set; } - public string[] Added { get; set; } = Array.Empty(); - public string[] Modified { get; set; } = Array.Empty(); - public string[] Removed { get; set; } = Array.Empty(); - } + public class CommitModel + { + public string Id { get; set; } + public string Message { get; set; } + public string[] Added { get; set; } = Array.Empty(); + public string[] Modified { get; set; } = Array.Empty(); + public string[] Removed { get; set; } = Array.Empty(); + } - public class InstallationModel - { - public long Id { get; set; } - } + public class InstallationModel + { + public long Id { get; set; } + } - public class RepositoryModel - { - public long Id { get; set; } + public class RepositoryModel + { + public long Id { get; set; } - [JsonProperty("default_branch")] - public string DefaultBranch { get; set; } - public string Name { get; set; } - } + [JsonProperty("default_branch")] + public string DefaultBranch { get; set; } + public string Name { get; set; } + } - public class PusherModel - { - public string Name { get; set; } - } + public class PusherModel + { + public string Name { get; set; } } } \ No newline at end of file diff --git a/Annoy-o-Bot/GitHub/Callbacks/GitHubCallbackRequest.cs b/Annoy-o-Bot/GitHub/Callbacks/GitHubCallbackRequest.cs index 509c7a6..895c210 100644 --- a/Annoy-o-Bot/GitHub/Callbacks/GitHubCallbackRequest.cs +++ b/Annoy-o-Bot/GitHub/Callbacks/GitHubCallbackRequest.cs @@ -5,12 +5,32 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; namespace Annoy_o_Bot.GitHub.Callbacks; public class GitHubCallbackRequest { - public static bool IsGitCommitCallback(HttpRequest callbackRequest, ILogger log) + public static async Task Validate(HttpRequest callbackRequest, string gitHubSecret, ILogger log) + { + if (!IsGitCommitCallback(callbackRequest, log)) + { + return null; + } + + if (!callbackRequest.Headers.TryGetValue("X-Hub-Signature-256", out var sha256SignatureHeaderValue)) + { + throw new Exception("Incoming callback request does not contain a 'X-Hub-Signature-256' header"); + } + + var requestBody = await new StreamReader(callbackRequest.Body).ReadToEndAsync(); + + await ValidateSignature(requestBody, gitHubSecret, sha256SignatureHeaderValue.ToString().Replace("sha256=", "")); + + return JsonConvert.DeserializeObject(requestBody); + } + + static bool IsGitCommitCallback(HttpRequest callbackRequest, ILogger log) { if (!callbackRequest.Headers.TryGetValue("X-GitHub-Event", out var callbackEvent) || callbackEvent != "push") { @@ -26,21 +46,7 @@ public static bool IsGitCommitCallback(HttpRequest callbackRequest, ILogger log) return true; } - - public static async Task Validate(HttpRequest callbackRequest, string gitHubSecret) - { - if (!callbackRequest.Headers.TryGetValue("X-Hub-Signature-256", out var sha256SignatureHeaderValue)) - { - throw new Exception("Incoming callback request does not contain a 'X-Hub-Signature-256' header"); - } - - var requestBody = await new StreamReader(callbackRequest.Body).ReadToEndAsync(); - - await ValidateSignature(requestBody, gitHubSecret, sha256SignatureHeaderValue.ToString().Replace("sha256=", "")); - return CallbackModel.FromJson(requestBody); - } - public static async Task ValidateSignature(string signedText, string secret, string sha256Signature) { var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(secret));