Пример #1
0
        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");
        }
Пример #2
0
        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);
        }
Пример #3
0
        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
                                                                                                      ));
        }
Пример #4
0
        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}"));
        }
Пример #5
0
        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));
        }
Пример #6
0
        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);
        }
Пример #7
0
        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));
        }
Пример #8
0
        /// <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));
        }
Пример #9
0
 private static void ValidateMethod(ApplicationLoadBalancerRequest request)
 {
     if (request.HttpMethod.ToLower() != "post")
     {
         throw new MethodNotAllowedException($"Method '{request.HttpMethod}' not allowed");
     }
 }
Пример #10
0
        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));
        }
Пример #11
0
        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);
        }
Пример #12
0
        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));
        }
Пример #13
0
 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();
     }
 }
Пример #14
0
        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}");
            }
        }
Пример #15
0
        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));
        }
Пример #16
0
        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;
        }
Пример #17
0
        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();
            }
        }
Пример #18
0
        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));
        }
Пример #19
0
        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));
        }
Пример #20
0
        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));
        }
Пример #21
0
        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));
        }
Пример #22
0
        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));
        }
Пример #23
0
        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);
        }
Пример #24
0
        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,
            });
        }
Пример #25
0
        /// <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);
        }
Пример #26
0
        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,
            };
        }
Пример #27
0
 public static Task <ApplicationLoadBalancerResponse> Execute(ApplicationLoadBalancerRequest request) =>
 mySampleApiCall.Execute(request);
Пример #28
0
 public ApplicationLoadBalancerResponse FunctionHandler(ApplicationLoadBalancerRequest appLoadBalancerEvent)
 {
     return(new ApplicationLoadBalancerResponse());
 }
Пример #29
0
 private ApplicationLoadBalancerResponse ApplicationLoadBalancerFunctionHandlerWithInput(ApplicationLoadBalancerRequest input, ILambdaContext context)
 {
     return(new ApplicationLoadBalancerResponse
     {
         StatusCode = (int)HttpStatusCode.OK,
         Headers = _singleValueHeaders
     });
 }
Пример #30
0
 public ApplicationLoadBalancerResponse Invoke(ApplicationLoadBalancerRequest request)
 => new ApplicationLoadBalancerResponse
 {
     StatusCode = 200,
     Body       = $"Hello {request.HttpMethod}: {request.Path}!"
 };