Example #1
0
def _process_sent_message(path: str, req_data: Dict[str, str], headers: Dict,
                          response: Response):
    """Extract one or multiple messages sent via SendMessage/SendMessageBatch from the given
    request/response data and forward them to the Lambda EventSourceListener for further processing"""

    queue_url = _queue_url(path, req_data, headers)
    action = req_data.get("Action")

    # extract data from XML response - assume data is wrapped in 2 parent elements
    response_data = xmltodict.parse(response.content)

    messages = []
    if action == "SendMessage":
        response_data = response_data["SendMessageResponse"][
            "SendMessageResult"]
        message = clone(req_data)
        message.update(response_data)
        messages.append(message)
    elif action == "SendMessageBatch":
        response_data = response_data["SendMessageBatchResponse"][
            "SendMessageBatchResult"]
        messages = parse_urlencoded_data(req_data,
                                         "SendMessageBatchRequestEntry")
        # Note: only forwarding messages from 'Successful', not from 'Failed' list
        entries = response_data.get("SendMessageBatchResultEntry") or []
        entries = ensure_list(entries)
        for successful in entries:
            msg = [m for m in messages if m["Id"] == successful["Id"]][0]
            msg.update(successful)

    event = {
        "QueueUrl": queue_url,
        "Messages": messages,
    }
    EventSourceListener.process_event_via_listener("sqs", event)
Example #2
0
def get_message_attributes(req_data):
    extracted_msg_attrs = parse_urlencoded_data(req_data,
                                                "MessageAttributes.entry")
    return prepare_message_attributes(extracted_msg_attrs)
Example #3
0
    def forward_request(self, method, path, data, headers):
        if method == "OPTIONS":
            return 200

        # check region
        try:
            aws_stack.check_valid_region(headers)
            aws_stack.set_default_region_in_headers(headers)
        except Exception as e:
            return make_error(message=str(e), code=400)

        if method == "POST":
            # parse payload and extract fields
            req_data = parse_qs(to_str(data), keep_blank_values=True)

            # parse data from query path
            if not req_data:
                parsed_path = urlparse(path)
                req_data = parse_qs(parsed_path.query, keep_blank_values=True)

            req_action = req_data["Action"][0]
            topic_arn = (req_data.get("TargetArn") or req_data.get("TopicArn")
                         or req_data.get("ResourceArn"))
            if topic_arn:
                topic_arn = topic_arn[0]
                topic_arn = aws_stack.fix_account_id_in_arns(topic_arn)
            if req_action == "SetSubscriptionAttributes":
                sub = get_subscription_by_arn(req_data["SubscriptionArn"][0])
                if not sub:
                    return make_error(
                        message="Unable to find subscription for given ARN",
                        code=400)

                attr_name = req_data["AttributeName"][0]
                attr_value = req_data["AttributeValue"][0]
                sub[attr_name] = attr_value
                return make_response(req_action)

            elif req_action == "GetSubscriptionAttributes":
                sub = get_subscription_by_arn(req_data["SubscriptionArn"][0])
                if not sub:
                    return make_error(
                        message="Subscription with arn {0} not found".format(
                            req_data["SubscriptionArn"][0]),
                        code=404,
                        code_string="NotFound",
                    )

                content = "<Attributes>"
                for key, value in sub.items():
                    if key in HTTP_SUBSCRIPTION_ATTRIBUTES:
                        continue
                    content += "<entry><key>%s</key><value>%s</value></entry>\n" % (
                        key,
                        value,
                    )
                content += "</Attributes>"
                return make_response(req_action, content=content)

            elif req_action == "Subscribe":
                if "Endpoint" not in req_data:
                    return make_error(
                        message="Endpoint not specified in subscription",
                        code=400)

                if req_data["Protocol"][0] not in SNS_PROTOCOLS:
                    return make_error(
                        message=
                        f"Invalid parameter: Amazon SNS does not support this protocol string: "
                        f"{req_data['Protocol'][0]}",
                        code=400,
                    )

                if ".fifo" in req_data["Endpoint"][
                        0] and ".fifo" not in topic_arn:
                    return make_error(
                        message=
                        "FIFO SQS Queues can not be subscribed to standard SNS topics",
                        code=400,
                        code_string="InvalidParameter",
                    )

            elif req_action == "ConfirmSubscription":
                if "TopicArn" not in req_data:
                    return make_error(
                        message=
                        "TopicArn not specified in confirm subscription request",
                        code=400,
                    )

                if "Token" not in req_data:
                    return make_error(
                        message=
                        "Token not specified in confirm subscription request",
                        code=400,
                    )

                do_confirm_subscription(
                    req_data.get("TopicArn")[0],
                    req_data.get("Token")[0])

            elif req_action == "Unsubscribe":
                if "SubscriptionArn" not in req_data:
                    return make_error(
                        message=
                        "SubscriptionArn not specified in unsubscribe request",
                        code=400,
                    )

                do_unsubscribe(req_data.get("SubscriptionArn")[0])

            elif req_action == "DeleteTopic":
                do_delete_topic(topic_arn)

            elif req_action == "Publish":
                if req_data.get("Subject") == [""]:
                    return make_error(code=400,
                                      code_string="InvalidParameter",
                                      message="Subject")
                if not req_data.get("Message") or all(
                        not message for message in req_data.get("Message")):
                    return make_error(code=400,
                                      code_string="InvalidParameter",
                                      message="Empty message")

                if topic_arn and ".fifo" in topic_arn and not req_data.get(
                        "MessageGroupId"):
                    return make_error(
                        code=400,
                        code_string="InvalidParameter",
                        message=
                        "The MessageGroupId parameter is required for FIFO topics",
                    )

                sns_backend = SNSBackend.get()
                # No need to create a topic to send SMS or single push notifications with SNS
                # but we can't mock a sending so we only return that it went well
                if "PhoneNumber" not in req_data and "TargetArn" not in req_data:
                    if topic_arn not in sns_backend.sns_subscriptions:
                        return make_error(
                            code=404,
                            code_string="NotFound",
                            message="Topic does not exist",
                        )

                message_id = publish_message(topic_arn, req_data, headers)

                # return response here because we do not want the request to be forwarded to SNS backend
                return make_response(req_action, message_id=message_id)

            elif req_action == "PublishBatch":
                entries = parse_urlencoded_data(
                    req_data, "PublishBatchRequestEntries.member",
                    "MessageAttributes.entry")

                if len(entries) > 10:
                    return make_error(
                        message=
                        "The batch request contains more entries than permissible",
                        code=400,
                        code_string="TooManyEntriesInBatchRequest",
                    )
                ids = [entry["Id"] for entry in entries]

                if len(set(ids)) != len(entries):
                    return make_error(
                        message=
                        "Two or more batch entries in the request have the same Id",
                        code=400,
                        code_string="BatchEntryIdsNotDistinct",
                    )

                if topic_arn and ".fifo" in topic_arn:
                    if not all(
                        ["MessageGroupId" in entry for entry in entries]):
                        return make_error(
                            message=
                            "The MessageGroupId parameter is required for FIFO topics",
                            code=400,
                            code_string="InvalidParameter",
                        )

                response = publish_batch(topic_arn, entries, headers)
                return requests_response_xml(
                    req_action,
                    response,
                    xmlns="http://sns.amazonaws.com/doc/2010-03-31/")

            elif req_action == "ListTagsForResource":
                tags = do_list_tags_for_resource(topic_arn)
                content = "<Tags/>"
                if len(tags) > 0:
                    content = "<Tags>"
                    for tag in tags:
                        content += "<member>"
                        content += "<Key>%s</Key>" % tag["Key"]
                        content += "<Value>%s</Value>" % tag["Value"]
                        content += "</member>"
                    content += "</Tags>"
                return make_response(req_action, content=content)

            elif req_action == "CreateTopic":
                sns_backend = SNSBackend.get()
                topic_arn = aws_stack.sns_topic_arn(req_data["Name"][0])
                tag_resource_success = self._extract_tags(
                    topic_arn, req_data, True, sns_backend)
                sns_backend.sns_subscriptions[topic_arn] = (
                    sns_backend.sns_subscriptions.get(topic_arn) or [])
                # in case if there is an error it returns an error , other wise it will continue as expected.
                if not tag_resource_success:
                    return make_error(
                        code=400,
                        code_string="InvalidParameter",
                        message="Topic already exists with different tags",
                    )

            elif req_action == "TagResource":
                sns_backend = SNSBackend.get()
                self._extract_tags(topic_arn, req_data, False, sns_backend)
                return make_response(req_action)

            elif req_action == "UntagResource":
                tags_to_remove = []
                req_tags = {
                    k: v
                    for k, v in req_data.items()
                    if k.startswith("TagKeys.member.")
                }
                req_tags = req_tags.values()
                for tag in req_tags:
                    tags_to_remove.append(tag[0])
                do_untag_resource(topic_arn, tags_to_remove)
                return make_response(req_action)

            data = self._reset_account_id(data)
            return Request(data=data, headers=headers, method=method)

        return True
Example #4
0
    def test_request_parsing(self):
        qs = (
            "Action=PutMetricAlarm&Version=2010-08-01&ComparisonOperator=GreaterThanOrEqualToThreshold&"
            +
            "EvaluationPeriods=1&AlarmActions.member.1=test123&AlarmDescription=Upper+threshold+scaling+alarm&"
            +
            "Metrics.member.1.Expression=e1%2F%28%281000%2A30%2A60%29%2F100%29&Metrics.member.1.Id=expr_1&"
            +
            "Metrics.member.2.Expression=FILL%28m1%2C0%29&Metrics.member.2.Id=e1&"
            + "Metrics.member.2.ReturnData=false&Metrics.member.3.Id=m1&" +
            "Metrics.member.3.MetricStat.Metric.Dimensions.member.1.Name=StreamName&"
            +
            "Metrics.member.3.MetricStat.Metric.Dimensions.member.1.Value=arn%3Aaws%3Akinesis%3A123&"
            +
            "Metrics.member.3.MetricStat.Metric.MetricName=PutRecords.TotalRecords&"
            +
            "Metrics.member.3.MetricStat.Metric.Namespace=AWS%2FKinesis&Metrics.member.3.MetricStat.Period=60&"
            +
            "Metrics.member.3.MetricStat.Stat=Sum&Metrics.member.3.ReturnData=false&Threshold=80&"
            +
            "AlarmName=mctesterson-application-tests-kds-fastpipe-stack-dataops-None-e8f05d1a"
        )

        expected = [
            {
                "Expression": "e1/((1000*30*60)/100)",
                "Id": "expr_1"
            },
            {
                "Expression": "FILL(m1,0)",
                "Id": "e1",
                "ReturnData": "false"
            },
            {
                "Id": "m1",
                "MetricStat": {
                    "Metric": {
                        "Dimensions.member": [{
                            "Name": "StreamName",
                            "Value": "arn:aws:kinesis:123"
                        }],
                        "MetricName":
                        "PutRecords.TotalRecords",
                        "Namespace":
                        "AWS/Kinesis",
                    },
                    "Period": "60",
                    "Stat": "Sum",
                },
                "ReturnData": "false",
            },
        ]

        response = BaseResponse()
        response.querystring = parse_qs(qs)
        result = response._get_multi_param("Metrics.member",
                                           skip_result_conversion=True)
        self.assertEqual(result, expected)

        # assert parsing via util
        result = parse_urlencoded_data(parse_qs(qs), "Metrics.member")
        self.assertEqual(result, expected)