mirror of
				https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
				synced 2025-10-31 10:15:33 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			149 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| namespace HaWeb.Controllers;
 | |
| using Microsoft.AspNetCore.Mvc;
 | |
| using HaWeb.FileHelpers;
 | |
| using System.Security.Cryptography;
 | |
| using System.Text;
 | |
| 
 | |
| [ApiController]
 | |
| [Route("api/webhook")]
 | |
| public class WebhookController : Controller {
 | |
|     private readonly IGitService _gitService;
 | |
|     private readonly IXMLFileProvider _xmlProvider;
 | |
|     private readonly IConfiguration _config;
 | |
|     private readonly ILogger<WebhookController> _logger;
 | |
| 
 | |
|     public WebhookController(
 | |
|         IGitService gitService,
 | |
|         IXMLFileProvider xmlProvider,
 | |
|         IConfiguration config,
 | |
|         ILogger<WebhookController> logger) {
 | |
|         _gitService = gitService;
 | |
|         _xmlProvider = xmlProvider;
 | |
|         _config = config;
 | |
|         _logger = logger;
 | |
|     }
 | |
| 
 | |
|     [HttpPost("git")]
 | |
|     public async Task<IActionResult> GitWebhook([FromHeader(Name = "X-Hub-Signature-256")] string? signature) {
 | |
|         try {
 | |
|             // Validate webhook secret if configured
 | |
|             var webhookSecret = _config.GetValue<string>("WebhookSecret");
 | |
|             if (!string.IsNullOrEmpty(webhookSecret)) {
 | |
|                 Request.EnableBuffering();
 | |
| 
 | |
|                 using var reader = new StreamReader(Request.Body, leaveOpen: true);
 | |
|                 var body = await reader.ReadToEndAsync();
 | |
|                 Request.Body.Position = 0;
 | |
| 
 | |
|                 _logger.LogInformation("Webhook received - Content-Type: {ContentType}, Body length: {Length}, Body SHA256: {BodyHash}",
 | |
|                     Request.ContentType, body.Length,
 | |
|                     Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(body))).ToLower());
 | |
| 
 | |
|                 if (!ValidateSignature(body, signature, webhookSecret)) {
 | |
|                     _logger.LogWarning("Webhook signature validation failed - check ValidateSignature logs above");
 | |
|                     return Unauthorized(new { error = "Invalid signature" });
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             _logger.LogInformation("Git webhook triggered, initiating pull...");
 | |
| 
 | |
|             // Pull latest changes
 | |
|             var hasChanges = _gitService.Pull();
 | |
| 
 | |
|             if (!hasChanges) {
 | |
|                 _logger.LogInformation("No changes detected after pull");
 | |
|                 return Ok(new {
 | |
|                     success = true,
 | |
|                     message = "Pull completed, no changes detected",
 | |
|                     hasChanges = false
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             _logger.LogInformation("Changes detected, triggering repository scan and parse...");
 | |
| 
 | |
|             // Trigger full reload: scan files, parse XML, generate and load Hamann.xml
 | |
|             _xmlProvider.Reload();
 | |
| 
 | |
|             var gitState = _gitService.GetGitState();
 | |
| 
 | |
|             _logger.LogInformation("Repository updated and library reloaded successfully to commit {Commit}", gitState?.Commit?[..7]);
 | |
| 
 | |
|             return Ok(new {
 | |
|                 success = true,
 | |
|                 message = "Repository pulled and parsed successfully",
 | |
|                 hasChanges = true,
 | |
|                 commit = gitState?.Commit,
 | |
|                 branch = gitState?.Branch,
 | |
|                 timestamp = gitState?.PullTime
 | |
|             });
 | |
|         }
 | |
|         catch (Exception ex) {
 | |
|             _logger.LogError(ex, "Error processing git webhook");
 | |
|             return StatusCode(500, new {
 | |
|                 success = false,
 | |
|                 error = ex.Message
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     [HttpGet("status")]
 | |
|     public IActionResult GetStatus() {
 | |
|         try {
 | |
|             var gitState = _gitService.GetGitState();
 | |
| 
 | |
|             if (gitState == null) {
 | |
|                 return NotFound(new { error = "Could not retrieve git state" });
 | |
|             }
 | |
| 
 | |
|             return Ok(new {
 | |
|                 commit = gitState.Commit,
 | |
|                 commitShort = gitState.Commit?[..7],
 | |
|                 branch = gitState.Branch,
 | |
|                 url = gitState.URL,
 | |
|                 timestamp = gitState.PullTime
 | |
|             });
 | |
|         }
 | |
|         catch (Exception ex) {
 | |
|             _logger.LogError(ex, "Error getting git status");
 | |
|             return StatusCode(500, new { error = ex.Message });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private bool ValidateSignature(string payload, string? signatureHeader, string secret) {
 | |
|         if (string.IsNullOrEmpty(signatureHeader)) {
 | |
|             _logger.LogWarning("Signature validation failed: No signature header provided");
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // GitHub uses HMAC-SHA256 with format "sha256=<hash>"
 | |
|         var prefix = "sha256=";
 | |
|         if (!signatureHeader.StartsWith(prefix)) {
 | |
|             _logger.LogWarning("Signature validation failed: Header doesn't start with '{Prefix}', got: {Header}", prefix, signatureHeader);
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         var expectedHash = signatureHeader.Substring(prefix.Length);
 | |
| 
 | |
|         using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
 | |
|         var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
 | |
|         var computedHash = Convert.ToHexString(hash).ToLower();
 | |
| 
 | |
|         // Test with GitHub's example values
 | |
|         var testPayload = "Hello, World!";
 | |
|         var testSecret = "It's a Secret to Everybody";
 | |
|         var testExpected = "757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17";
 | |
|         using var testHmac = new HMACSHA256(Encoding.UTF8.GetBytes(testSecret));
 | |
|         var testHash = testHmac.ComputeHash(Encoding.UTF8.GetBytes(testPayload));
 | |
|         var testComputed = Convert.ToHexString(testHash).ToLower();
 | |
|         _logger.LogWarning("GitHub test case - Expected: {Expected}, Computed: {Computed}, Match: {Match}",
 | |
|             testExpected, testComputed, testExpected == testComputed);
 | |
| 
 | |
|         _logger.LogWarning("Signature validation - Expected: {Expected}, Computed: {Computed}, Match: {Match}",
 | |
|             expectedHash, computedHash, expectedHash == computedHash);
 | |
| 
 | |
|         return CryptographicOperations.FixedTimeEquals(
 | |
|             Encoding.UTF8.GetBytes(computedHash),
 | |
|             Encoding.UTF8.GetBytes(expectedHash)
 | |
|         );
 | |
|     }
 | |
| } | 
