Beispiel #1
0
        // [Route("Subscribe")]
        public async Task <AcknowledgeAction> SubscribeAsync(SubscribeAction action)
        {
            var connectionId = CurrentRequest.RequestContext.ConnectionId;

            LogInfo($"Subscribe request from: {connectionId}");

            // validate request
            if (string.IsNullOrEmpty(action.Rule))
            {
                return(action.AcknowledgeError("missing or invalid rule name"));
            }

            // retrieve connection record
            var connection = await _dataTable.GetConnectionRecordAsync(connectionId);

            if (connection == null)
            {
                LogInfo("Connection was removed");
                return(action.AcknowledgeError("connection gone"));
            }
            if (connection.State == ConnectionState.Failed)
            {
                LogInfo("Connection is in failed state");
                throw Abort(action.AcknowledgeError("connection reset required"));
            }
            if (connection.SubscriptionArn == null)
            {
                LogInfo("Client has not announced itself");
                return(action.AcknowledgeError("client is unannounced"));
            }
            if (connection.State != ConnectionState.Open)
            {
                LogInfo("Connection is not open (state: {0})", connection.State);
                throw Abort(action.AcknowledgeError("action not allowed"));
            }

            // validate pattern
            var validPattern = false;

            try {
                validPattern = EventPatternMatcher.IsValid(JObject.Parse(action.Pattern));
            } catch {
                // nothing to do
            }
            if (!validPattern)
            {
                return(action.AcknowledgeError("invalid pattern"));
            }

            // create or update event rule
            await _dataTable.CreateOrUpdateRuleRecordAsync(new RuleRecord {
                Rule         = action.Rule,
                Pattern      = action.Pattern,
                ConnectionId = connection.ConnectionId
            });

            return(action.AcknowledgeOk());
        }
Beispiel #2
0
        public void Pattern_with_null_serialized_is_valid()
        {
            // arrange
            var pattern = JObject.Parse("{\"source\":[\"Sample.BlazorEventsSample:1.0-DEV@stevebv7-lambdasharp-cor-deploymentbucketresource-9h53iqcat7uj::MyBlazorApp\"],\"detail-type\":[\"Sample.BlazorEventsSample.MyBlazorApp.Shared.TodoItem\"],\"resources\":[\"lambdasharp:tier:SteveBv7\"],\"detail\":null}");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeTrue();
        }
Beispiel #3
0
        public void Empty_pattern_is_not_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{}");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeFalse();
        }
Beispiel #4
0
        public void Pattern_with_invalid_filter_is_not_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""Bar"": 42 } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeFalse();
        }
Beispiel #5
0
        public void Pattern_with_exists_null_is_not_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""exists"": null } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeFalse();
        }
Beispiel #6
0
        public void Pattern_with_cidr_bad_prefix_is_not_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""cidr"": ""1.1.1.1/42"" } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeFalse();
        }
Beispiel #7
0
        public void Pattern_with_numeric_two_operations_is_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""numeric"": [ "">"", 42, ""<"", 404 ] } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeTrue();
        }
Beispiel #8
0
        public void Pattern_with_anything_but_content_filter_is_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""anything-but"": { ""prefix"": ""Bar"" } } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeTrue();
        }
Beispiel #9
0
        public void Pattern_with_anything_but_numeric_is_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""anything-but"": 42 } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeTrue();
        }
Beispiel #10
0
        public void Pattern_with_prefix_list_is_not_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ { ""prefix"": [ ""Bar"" ] } ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeFalse();
        }
Beispiel #11
0
        public void Pattern_with_null_is_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ null ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeTrue();
        }
Beispiel #12
0
        public void Pattern_with_nested_list_is_not_valid()
        {
            // arrange
            var pattern = JObject.Parse(@"{
                ""Foo"": [ [ 42 ]  ]
            }");

            // act
            var isValid = EventPatternMatcher.IsValid(pattern);

            // assert
            isValid.Should().BeFalse();
        }
Beispiel #13
0
        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();
        }
Beispiel #14
0
        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();
        }
Beispiel #15
0
        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();
        }
Beispiel #16
0
        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();
        }
Beispiel #17
0
        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();
        }
Beispiel #18
0
        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();
        }
Beispiel #19
0
        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();
        }
Beispiel #20
0
        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();
        }
Beispiel #21
0
        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();
        }
Beispiel #22
0
        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
            };
        }
Beispiel #23
0
        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"));
        }
Beispiel #24
0
        // [Route("Subscribe")]
        public async Task <AcknowledgeAction> SubscribeAsync(SubscribeAction action)
        {
            var connectionId = CurrentRequest.RequestContext.ConnectionId;

            LogInfo($"Subscribe request from: {connectionId}");

            // validate request
            if (string.IsNullOrEmpty(action.Rule))
            {
                return(new AcknowledgeAction {
                    RequestId = action.RequestId,
                    Status = "Error",
                    Message = "Missing or invalid rule name"
                });
            }

            // retrieve websocket connection record
            var connection = await _dataTable.GetConnectionAsync(connectionId);

            if (connection == null)
            {
                LogInfo("Connection was removed");
                return(new AcknowledgeAction {
                    RequestId = action.RequestId,
                    Rule = action.Rule,
                    Status = "Error",
                    Message = "Connection gone"
                });
            }

            // validate pattern
            var validPattern = false;

            try {
                validPattern = EventPatternMatcher.IsValid(JObject.Parse(action.Pattern));
            } catch {
                // nothing to do
            }
            if (!validPattern)
            {
                return(new AcknowledgeAction {
                    RequestId = action.RequestId,
                    Rule = action.Rule,
                    Status = "Error",
                    Message = "Invalid pattern"
                });
            }

            // create or update event rule
            await _dataTable.CreateOrUpdateRuleAsync(new RuleRecord {
                Rule         = action.Rule,
                Pattern      = action.Pattern,
                ConnectionId = connection.ConnectionId
            });

            return(new AcknowledgeAction {
                RequestId = action.RequestId,
                Rule = action.Rule,
                Status = "Ok"
            });
        }