public void Event_with_not_exists_is_matched() { // arrange var evt = JObject.Parse(@"{}"); var pattern = JObject.Parse(@"{ ""Foo"": [ { ""exists"": false } ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeTrue(); }
public void Empty_event_is_not_matched() { // arrange var evt = JObject.Parse(@"{}"); var pattern = JObject.Parse(@"{ ""Foo"": [ ""Bar"" ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeFalse(); }
public void Event_with_prefix_is_not_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": ""Bar"" }"); var pattern = JObject.Parse(@"{ ""Foo"": [ { ""prefix"": ""F"" } ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeFalse(); }
public void Event_with_list_is_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": [ ""Bar"" ] }"); var pattern = JObject.Parse(@"{ ""Foo"": [ ""Bar"" ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeTrue(); }
public void Event_with_cidr_mismatch_is_not_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": ""Bar"" }"); var pattern = JObject.Parse(@"{ ""Foo"": [ { ""cidr"": ""192.168.1.1/24"" } ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeFalse(); }
public void Event_with_numeric_one_operation_type_mismatch_is_not_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": ""Bar"" }"); var pattern = JObject.Parse(@"{ ""Foo"": [ { ""numeric"": [ "">="", 40 ] } ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeFalse(); }
public void Event_with_numeric_two_operation_is_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": 42 }"); var pattern = JObject.Parse(@"{ ""Foo"": [ { ""numeric"": [ "">="", 40, ""<"", 404 ] } ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeTrue(); }
public void Event_with_anything_but_is_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": ""Bar"" }"); var pattern = JObject.Parse(@"{ ""Foo"": [ { ""anything-but"": { ""prefix"": ""F"" } } ] }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeTrue(); }
public void Event_with_nested_prefix_is_matched() { // arrange var evt = JObject.Parse(@"{ ""Foo"": { ""Bar"": ""ABC"" } }"); var pattern = JObject.Parse(@"{ ""Foo"": { ""Bar"": [ { ""prefix"": ""A"" } ] } }"); // act var isMatch = EventPatternMatcher.IsMatch(evt, pattern); // assert isMatch.Should().BeTrue(); }
public override async Task <APIGatewayHttpApiV2ProxyResponse> ProcessMessageAsync(APIGatewayHttpApiV2ProxyRequest request) { LogInfo($"Message received at {request.RequestContext.Http.Method}:{request.RawPath}?{request.RawQueryString}"); // validate invocation method if (request.RequestContext.Http.Method != "POST") { LogInfo("Unsupported request method {0}", request.RequestContext.Http.Method); return(BadRequest()); } // validate request token if ( !request.QueryStringParameters.TryGetValue("token", out var token) || (token != _httpApiToken) ) { LogInfo("Missing or invalid request token"); return(BadRequest()); } // validate request websocket if ( !request.QueryStringParameters.TryGetValue("ws", out var connectionId) || string.IsNullOrEmpty(connectionId) ) { LogInfo("Invalid websocket connection id"); return(BadRequest()); } // validate request id if ( !request.QueryStringParameters.TryGetValue("rid", out var requestId) || string.IsNullOrEmpty(requestId) ) { LogInfo("Invalid request id"); return(BadRequest()); } // check if request is a subscription confirmation var topicSubscription = LambdaSerializer.Deserialize <TopicSubscriptionPayload>(request.Body); if (topicSubscription.Type == "SubscriptionConfirmation") { // confirm it's for the expected topic ARN if (topicSubscription.TopicArn != _eventTopicArn) { LogWarn("Wrong Topic ARN for subscription confirmation (Expected: {0}, Received: {1})", _eventTopicArn, topicSubscription.TopicArn); return(BadRequest()); } // confirm subscription await HttpClient.GetAsync(topicSubscription.SubscribeURL); // send welcome action to websocket connection await SendMessageToConnection(new AcknowledgeAction { RequestId = requestId, Status = "Ok" }, connectionId); return(Success("Confirmed")); } // validate SNS message var snsMessage = LambdaSerializer.Deserialize <SNSEvent.SNSMessage>(request.Body); if (snsMessage.Message == null) { LogWarn("Invalid SNS message received: {0}", request.Body); return(BadRequest()); } // validate CloudWatch event var cloudWatchEvent = LambdaSerializer.Deserialize <CloudWatchEventPayload>(snsMessage.Message); if ( (cloudWatchEvent.Source == null) || (cloudWatchEvent.DetailType == null) || (cloudWatchEvent.Resources == null) ) { LogInfo("Invalid CloudWatch event received: {0}", snsMessage.Message); return(BadRequest()); } // check if the keep-alive event was received if ( (cloudWatchEvent.Source == "aws.events") && (cloudWatchEvent.DetailType == "Scheduled Event") && (cloudWatchEvent.Resources.Count == 1) && (cloudWatchEvent.Resources[0] == _keepAliveRuleArn) ) { // send keep-alive action to websocket connection await SendMessageToConnection(new KeepAliveAction(), connectionId); return(Success("Ok")); } // determine what rules are matching JObject evt; try { evt = JObject.Parse(snsMessage.Message); } catch (Exception e) { LogError(e, "invalid message"); return(BadRequest()); } var rules = await _dataTable.GetConnectionRulesAsync(connectionId); var matchedRules = rules .Where(rule => { try { var pattern = JObject.Parse(rule.Pattern); return(EventPatternMatcher.IsMatch(evt, pattern)); } catch (Exception e) { LogError(e, "invalid event pattern: {0}", rule.Pattern); return(false); } }).Select(rule => rule.Rule) .ToList(); if (matchedRules.Any()) { await SendMessageToConnection( new EventAction { Rules = matchedRules, Source = cloudWatchEvent.Source, Type = cloudWatchEvent.DetailType, Event = snsMessage.Message }, connectionId ); } return(Success("Ok")); // local functions APIGatewayHttpApiV2ProxyResponse Success(string message) => new APIGatewayHttpApiV2ProxyResponse { Body = message, Headers = new Dictionary <string, string> { ["Content-Type"] = "text/plain" }, StatusCode = (int)HttpStatusCode.OK }; APIGatewayHttpApiV2ProxyResponse BadRequest() => new APIGatewayHttpApiV2ProxyResponse { Body = "Bad Request", Headers = new Dictionary <string, string> { ["Content-Type"] = "text/plain" }, StatusCode = (int)HttpStatusCode.BadRequest }; }
private async Task <APIGatewayHttpApiV2ProxyResponse> DispatchEvent(string connectionId, string message) { // validate EventBridge event var eventBridgeEvent = LambdaSerializer.Deserialize <EventBridgeventPayload>(message); if ( (eventBridgeEvent.Source == null) || (eventBridgeEvent.DetailType == null) || (eventBridgeEvent.Resources == null) ) { LogInfo("Invalid EventBridge event received: {0}", message); return(BadRequestResponse()); } // check if the keep-alive event was received if ( (eventBridgeEvent.Source == "aws.events") && (eventBridgeEvent.DetailType == "Scheduled Event") && (eventBridgeEvent.Resources?.Count == 1) && (eventBridgeEvent.Resources[0] == _keepAliveRuleArn) ) { // retrieve connection record var connection = await _dataTable.GetConnectionRecordAsync(connectionId); if (connection == null) { return(SuccessResponse("Gone")); } if (connection.State != ConnectionState.Open) { return(SuccessResponse("Ignored")); } // send keep-alive action to websocket connection LogInfo("KeepAlive tick"); await SendActionToConnection(new KeepAliveAction(), connectionId); return(SuccessResponse("Ok")); } // determine what rules are matching the event JObject evt; try { evt = JObject.Parse(message); } catch (Exception e) { LogError(e, "invalid message"); return(BadRequestResponse()); } var rules = await _dataTable.GetAllRuleRecordAsync(connectionId); var matchedRules = rules .Where(rule => { try { var pattern = JObject.Parse(rule.Pattern); return(EventPatternMatcher.IsMatch(evt, pattern)); } catch (Exception e) { LogError(e, "invalid event pattern: {0}", rule.Pattern); return(false); } }).Select(rule => rule.Rule) .ToList(); if (matchedRules.Any()) { LogInfo($"Event matched {matchedRules.Count():N0} rules: {string.Join(", ", matchedRules)}"); await SendActionToConnection( new EventAction { Rules = matchedRules, Source = eventBridgeEvent.Source, Type = eventBridgeEvent.DetailType, Event = message }, connectionId ); } else { LogInfo("Event matched no rules"); } return(SuccessResponse("Ok")); }