public async Task ShouldReturnCorrectStatusDescription() { var tokenHash = "tokenHash"; var action = "approve"; var request = new ApplicationLoadBalancerRequest { QueryStringParameters = new Dictionary <string, string> { ["token"] = tokenHash, ["action"] = action, ["pipeline"] = pipeline, } }; var stepFunctionsClient = Substitute.For <IAmazonStepFunctions>(); var s3Client = Substitute.For <IAmazonS3>(); var s3GetObjectFacade = Substitute.For <S3GetObjectFacade>(); var logger = Substitute.For <ILogger <Handler> >(); var handler = new Handler(stepFunctionsClient, s3Client, s3GetObjectFacade, config, logger); s3GetObjectFacade.GetObject <ApprovalInfo>(Arg.Any <string>(), Arg.Any <string>()).Returns(approvalInfo); var response = await handler.Handle(request); response.StatusDescription.Should().Be("200 OK"); }
public virtual GithubEvent Validate(ApplicationLoadBalancerRequest request) { GithubEvent payload = null !; string eventType; ValidateMethod(request); ValidateEvent(request, out eventType); switch (eventType) { case "push": { ValidatePushBodyFormat(request, out var pushPayload); payload = pushPayload; break; } case "pull_request": { ValidatePullRequestBodyFormat(request, out var pullPayload); payload = pullPayload; break; } } ValidateContentsUrlPresent(payload); ValidateOwner(payload); ValidateSignature(request); return(payload); }
public async Task ShouldCallSendTaskSuccess() { var tokenHash = "tokenHash"; var action = "approve"; var serializedOutput = Serialize(new { Action = action }); var request = new ApplicationLoadBalancerRequest { QueryStringParameters = new Dictionary <string, string> { ["token"] = tokenHash, ["action"] = action, ["pipeline"] = pipeline, } }; var stepFunctionsClient = Substitute.For <IAmazonStepFunctions>(); var s3Client = Substitute.For <IAmazonS3>(); var s3GetObjectFacade = Substitute.For <S3GetObjectFacade>(); var logger = Substitute.For <ILogger <Handler> >(); var handler = new Handler(stepFunctionsClient, s3Client, s3GetObjectFacade, config, logger); s3GetObjectFacade.GetObject <ApprovalInfo>(Arg.Any <string>(), Arg.Any <string>()).Returns(approvalInfo); await handler.Handle(request); await stepFunctionsClient.Received().SendTaskSuccessAsync(Arg.Is <SendTaskSuccessRequest>(req => req.TaskToken == token && req.Output == serializedOutput )); }
public async Task ShouldDeleteApprovalInfo() { var tokenHash = "tokenHash"; var action = "approve"; var serializedOutput = Serialize(new { Action = action }); var request = new ApplicationLoadBalancerRequest { QueryStringParameters = new Dictionary <string, string> { ["token"] = tokenHash, ["action"] = action, ["pipeline"] = pipeline, } }; var stepFunctionsClient = Substitute.For <IAmazonStepFunctions>(); var s3Client = Substitute.For <IAmazonS3>(); var s3GetObjectFacade = Substitute.For <S3GetObjectFacade>(); var logger = Substitute.For <ILogger <Handler> >(); var handler = new Handler(stepFunctionsClient, s3Client, s3GetObjectFacade, config, logger); s3GetObjectFacade.GetObject <ApprovalInfo>(Arg.Any <string>(), Arg.Any <string>()).Returns(approvalInfo); await handler.Handle(request); await s3Client.Received().DeleteObjectAsync(Arg.Is(bucket), Arg.Is($"{pipeline}/approvals/{tokenHash}")); }
public void RequestsWithBadSignatureThrowException() { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = "push", ["x-hub-signature"] = "sha1=81e2a24bcf4284e90324378736dcb27a43cc79ed" }, Body = Serialize(new PushEvent { Repository = new Repository { ContentsUrl = "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}" } }) }; var options = Options.Create(new Config { GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.InstanceOf <InvalidSignatureException>(), () => requestValidator.Validate(request)); }
public void LambdaWrapper_ForApplicationLoadBalancerWithInput() { var albRequestContext = new ApplicationLoadBalancerRequest.ALBRequestContext { Elb = new ApplicationLoadBalancerRequest.ElbInfo() }; albRequestContext.Elb.TargetGroupArn = TestArn; var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Path = "/test/path", Headers = _singleValueHeaders, RequestContext = albRequestContext }; var context = new TestLambdaContext { AwsRequestId = "testId", InvokedFunctionArn = TestArn }; var wrappedHandler = new TracingRequestHandler().LambdaWrapper(ApplicationLoadBalancerFunctionHandlerWithInput, request, context); var span = _tracer.FinishedSpans()[0]; Assert.That((string)span.Tags["aws.requestId"], Is.EqualTo("testId")); Assert.That((string)span.Tags["aws.arn"], Is.EqualTo(TestArn)); Assert.That((string)span.Tags["response.status"], Is.EqualTo("200")); Assert.That(span.Tags.ContainsKey("newrelic"), Is.False); }
public void RequestsWithGoodSignatureDontThrow() { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = "push", ["x-hub-signature"] = "sha1=9df818278c8fd1d5013f435a6f135f47238c71f7" }, Body = Serialize(new PushEvent { Repository = new Repository { ContentsUrl = "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}" } }) }; var options = Options.Create(new Config { GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.Not.InstanceOf <InvalidSignatureException>(), () => requestValidator.Validate(request)); }
/// <summary> /// This function is called on every request to /webhooks/github /// </summary> /// <param name="request">Request sent by the application load balancer</param> /// <param name="context">The lambda context</param> /// <returns>A load balancer response object</returns> public async Task <ApplicationLoadBalancerResponse> Handle(ApplicationLoadBalancerRequest request, CancellationToken cancellationToken = default) { GithubEvent payload = null; try { payload = requestValidator.Validate(request); } catch (RequestValidationException e) { logger.LogError(e.Message); return(CreateResponse(statusCode: e.StatusCode)); } var commitMessage = await commitMessageFetcher.FetchCommitMessage(payload); payload.HeadCommitMessage = commitMessage; IEnumerable <Task> GetTasks() { switch (payload) { case PushEvent pushEvent: return(GetPushEventTasks(pushEvent)); case PullRequestEvent prEvent: return(GetPullRequestEventTasks(prEvent)); } return(Array.Empty <Task>()); } var tasks = GetTasks(); await Task.WhenAll(tasks); return(CreateResponse(statusCode: HttpStatusCode.OK)); }
private static void ValidateMethod(ApplicationLoadBalancerRequest request) { if (request.HttpMethod.ToLower() != "post") { throw new MethodNotAllowedException($"Method '{request.HttpMethod}' not allowed"); } }
public void RequestsWithUnexpectedOwnerThrowException([ValueSource("UnexpectedOwnerRequests")] ApplicationLoadBalancerRequest request) { var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.InstanceOf <UnexpectedOwnerException>(), () => requestValidator.Validate(request)); }
public void TestSampleFunction() { var function = new Function(); var context = new TestLambdaContext(); var request = new ApplicationLoadBalancerRequest(); var response = function.FunctionHandler(request, context); Assert.Equal(200, response.StatusCode); Assert.Contains("Hello World from Lambda", response.Body); }
public void NonPostRequestsThrowMethodNotAllowed([Values("GET", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS")] string method) { var request = new ApplicationLoadBalancerRequest { HttpMethod = method }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.InstanceOf <MethodNotAllowedException>(), () => requestValidator.Validate(request)); }
private static void ValidatePushBodyFormat(ApplicationLoadBalancerRequest request, out PushEvent payload) { try { payload = Deserialize <PushEvent>(request.Body); } catch (Exception e) { Console.WriteLine(e.Message + "\n" + e.StackTrace); throw new BodyNotJsonException(); } }
private void ValidateSignature(ApplicationLoadBalancerRequest request) { string givenSignature = null; string actualSignature = null; request.Headers.TryGetValue("x-hub-signature", out givenSignature); actualSignature = ComputeSignature(request.Body); if (givenSignature != actualSignature) { throw new InvalidSignatureException($"Signatures do not match. Actual signature: {actualSignature}. Given signature: {givenSignature}"); } }
public void PostRequestsDontThrowMethodNotAllowed() { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST" }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.Not.InstanceOf <MethodNotAllowedException>(), () => requestValidator.Validate(request)); }
private static void ValidateEvent(ApplicationLoadBalancerRequest request, out string eventType) { string @event = ""; request.Headers?.TryGetValue("x-github-event", out @event); @event = @event.ToLower(); if (@event != "push" && @event != "pull_request") { throw new EventNotAllowedException($"Event '{@event}' not allowed"); } eventType = @event; }
private static void ValidatePullRequestBodyFormat(ApplicationLoadBalancerRequest request, out PullRequestEvent payload) { try { payload = Deserialize <PullRequestEvent>(request.Body); } catch (Exception e) { Console.WriteLine(e.Message + "\n" + e.StackTrace); throw new BodyNotJsonException(); } if (payload.Action != "opened" && payload.Action != "synchronize") { throw new ActionNotAllowedException(); } }
public void AllowedEventsDontThrow([Values("push", "pull_request")] string @event) { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = @event }, Body = "{}" }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.Not.InstanceOf <EventNotAllowedException>(), () => requestValidator.Validate(request)); }
public void OnlyPushAndPullRequestEventsAreAllowed([Values("package_pushed")] string evnt) { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = evnt }, Body = "{}" }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.InstanceOf <EventNotAllowedException>(), () => requestValidator.Validate(request)); }
public void JsonRequestsDontThrowBodyNotJson() { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = "push" }, Body = "{}" }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.Not.InstanceOf <BodyNotJsonException>(), () => requestValidator.Validate(request)); }
public void PullRequestEventsThatAreNotOpenedOrSynchronizeActionsThrow() { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = "pull_request" }, Body = @"{ ""action"": ""bad"" }" }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.InstanceOf <ActionNotAllowedException>(), () => requestValidator.Validate(request)); }
public void RequestsWithoutContentsThrowException() { var request = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Headers = new Dictionary <string, string> { ["x-github-event"] = "push" }, Body = Serialize(new PushEvent { Repository = new Repository { } }) }; var options = Options.Create(new Config { GithubOwner = "Codertocat", GithubSigningSecret = "test_key" }); var requestValidator = new RequestValidator(options); Assert.Throws(Is.InstanceOf <NoContentsUrlException>(), () => requestValidator.Validate(request)); }
public static ApplicationLoadBalancerResponse FunctionHandler(ApplicationLoadBalancerRequest request, ILambdaContext context) { System.Console.WriteLine(request.Body.ToString() ?? "Empty Request"); string body = FakeResponseMaker.BuildFakeResponse(); var responseBody = body; Dictionary <string, string> Headers = new Dictionary <string, string>(); Headers.Add("Content-Type", "text/html;"); ApplicationLoadBalancerResponse response = new ApplicationLoadBalancerResponse() { IsBase64Encoded = false, StatusCode = 200, StatusDescription = "200 OK", Headers = Headers, Body = responseBody }; return(response); }
public async Task <ApplicationLoadBalancerResponse> Handle(ApplicationLoadBalancerRequest request, CancellationToken cancellationToken = default) { var action = request.QueryStringParameters["action"]; var pipeline = request.QueryStringParameters["pipeline"]; var tokenHash = request.QueryStringParameters["token"]; var key = $"{pipeline}/approvals/{tokenHash}"; var bucket = config.StateStore; var approvalInfo = await s3GetObjectFacade.GetObject <ApprovalInfo>(bucket, key); var sendTaskResponse = await stepFunctionsClient.SendTaskSuccessAsync(new SendTaskSuccessRequest { TaskToken = approvalInfo.Token, Output = Serialize(new { Action = action, }) }); logger.LogInformation($"Send task success response: {Serialize(sendTaskResponse)}"); var deleteResponse = await s3Client.DeleteObjectAsync(bucket, key); logger.LogInformation($"Received delete response: {Serialize(deleteResponse)}"); var body = action == "approve" ? "approved" : "rejected"; return(new ApplicationLoadBalancerResponse { StatusCode = 200, StatusDescription = "200 OK", Headers = new Dictionary <string, string> { ["content-type"] = "text/plain" }, Body = body, IsBase64Encoded = false, }); }
/// <summary> /// Lambda function handler to respond to events coming from an Application Load Balancer. /// /// Note: If "Multi value headers" is disabled on the ELB Target Group then use the Headers and QueryStringParameters properties /// on the ApplicationLoadBalancerRequest and ApplicationLoadBalancerResponse objects. If "Multi value headers" is enabled then /// use MultiValueHeaders and MultiValueQueryStringParameters properties. /// </summary> /// <param name="request"></param> /// <param name="context"></param> /// <returns></returns> public ApplicationLoadBalancerResponse FunctionHandler(ApplicationLoadBalancerRequest request, ILambdaContext context) { var response = new ApplicationLoadBalancerResponse { StatusCode = 200, StatusDescription = "200 OK", IsBase64Encoded = false }; // If "Multi value headers" is enabled for the ELB Target Group then use the "response.MultiValueHeaders" property instead of "response.Headers". response.Headers = new Dictionary <string, string> { { "Content-Type", "text/html; charset=utf-8" } }; response.Body = @" <html> <head> <title>Hello World!</title> <style> html, body { margin: 0; padding: 0; font-family: arial; font-weight: 700; font-size: 3em; text-align: center; } </style> </head> <body> <p>Hello World from Lambda</p> </body> </html> "; return(response); }
public void Setup() { // APIGatewayProxy _baseAPIGatewayProxyRequest = new APIGatewayProxyRequest { HttpMethod = "POST", Path = "/test/path", }; _baseAPIGatewayProxyResponse = new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, }; // ApplicationLoadBalancer var albRequestContext = new ApplicationLoadBalancerRequest.ALBRequestContext { Elb = new ApplicationLoadBalancerRequest.ElbInfo() }; albRequestContext.Elb.TargetGroupArn = TestArn; _baseApplicationLoadBalancerRequest = new ApplicationLoadBalancerRequest { HttpMethod = "POST", Path = "/test/path", RequestContext = albRequestContext, }; _baseApplicationLoadBalancerResponse = new ApplicationLoadBalancerResponse { StatusCode = (int)HttpStatusCode.OK, }; // SQSEvent var sqsRecord = new SQSEvent.SQSMessage { EventSourceArn = TestArn }; _baseSQSEvent = new SQSEvent { Records = new List <SQSEvent.SQSMessage> { sqsRecord }, }; // SNSEvent var snsMessaage = new SNSEvent.SNSMessage() { Message = "Test Message", }; var snsRecord = new SNSEvent.SNSRecord { EventSubscriptionArn = TestArn, Sns = snsMessaage }; _baseSNSEvent = new SNSEvent { Records = new List <SNSEvent.SNSRecord> { snsRecord }, }; // KinesisEvent var kinesisRecord = new KinesisEvent.KinesisEventRecord { EventSourceARN = TestArn }; _baseKinesisEvent = new KinesisEvent { Records = new List <KinesisEvent.KinesisEventRecord> { kinesisRecord }, }; // S3Event var s3Record = new Amazon.S3.Util.S3EventNotification.S3EventNotificationRecord { S3 = new Amazon.S3.Util.S3EventNotification.S3Entity { Bucket = new Amazon.S3.Util.S3EventNotification.S3BucketEntity { Arn = TestArn } } }; _baseS3Event = new S3Event { Records = new List <Amazon.S3.Util.S3EventNotification.S3EventNotificationRecord> { s3Record }, }; // DynamoDBEvent var dynamoDBRecord = new DynamoDBEvent.DynamodbStreamRecord { EventSourceArn = TestArn }; _baseDynamoDBEvent = new DynamoDBEvent { Records = new List <DynamoDBEvent.DynamodbStreamRecord> { dynamoDBRecord }, }; // KinesisFirehoseEvent _baseKinesisFirehoseEvent = new KinesisFirehoseEvent { DeliveryStreamArn = TestArn, }; }
public static Task <ApplicationLoadBalancerResponse> Execute(ApplicationLoadBalancerRequest request) => mySampleApiCall.Execute(request);
public ApplicationLoadBalancerResponse FunctionHandler(ApplicationLoadBalancerRequest appLoadBalancerEvent) { return(new ApplicationLoadBalancerResponse()); }
private ApplicationLoadBalancerResponse ApplicationLoadBalancerFunctionHandlerWithInput(ApplicationLoadBalancerRequest input, ILambdaContext context) { return(new ApplicationLoadBalancerResponse { StatusCode = (int)HttpStatusCode.OK, Headers = _singleValueHeaders }); }
public ApplicationLoadBalancerResponse Invoke(ApplicationLoadBalancerRequest request) => new ApplicationLoadBalancerResponse { StatusCode = 200, Body = $"Hello {request.HttpMethod}: {request.Path}!" };