def _fix_account_id(response): return aws_stack.fix_account_id_in_arns(response, existing=MOTO_ACCOUNT_ID, replace=TEST_AWS_ACCOUNT_ID)
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
def _reset_account_id(data): """ Fix account ID in request payload. All external-facing responses contain our predefined account ID (defaults to 000000000000), whereas the backend endpoint from moto expects a different hardcoded account ID (123456789012). """ return aws_stack.fix_account_id_in_arns( data, colon_delimiter='%3A', existing=TEST_AWS_ACCOUNT_ID, replace=MOTO_ACCOUNT_ID)
def SNS_Topic_get_cfn_attribute(self, attribute_name): result = SNS_Topic_get_cfn_attribute(self, attribute_name) if attribute_name.lower() in ['arn', 'topicarn']: result = aws_stack.fix_account_id_in_arns(result) return result
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 = urlparse.parse_qs(to_str(data), keep_blank_values=True) # parse data from query path if not req_data: parsed_path = urlparse.urlparse(path) req_data = urlparse.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='Unable to find subscription for given ARN', code=400) content = '<Attributes>' for key, value in sub.items(): 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) 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') # 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_SUBSCRIPTIONS.keys(): return make_error(code=404, code_string='NotFound', message='Topic does not exist') message_id = publish_message(topic_arn, req_data) # 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 == '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': topic_arn = aws_stack.sns_topic_arn(req_data['Name'][0]) tag_resource_success = self._extract_tags( topic_arn, req_data, True) # 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': self._extract_tags(topic_arn, req_data, False) 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
def fix_account_id(response): return aws_stack.fix_account_id_in_arns( response, replace=TEST_AWS_ACCOUNT_ID)
def return_response(self, method, path, data, headers, response): if response.status_code >= 400: LOG.debug('Error response from CloudFormation (%s) %s %s: %s' % (response.status_code, method, path, response.content)) if response._content: aws_stack.fix_account_id_in_arns(response)
def fix_ids(o, **kwargs): if isinstance(o, dict): for k, v in o.items(): if common.is_string(v, exclude_binary=True): o[k] = aws_stack.fix_account_id_in_arns(v) return o
def deploy_resource(resource_id, resources, stack_name): resource = resources[resource_id] client = get_client(resource) if not client: return False resource_type = get_resource_type(resource) func_details = RESOURCE_TO_FUNCTION.get(resource_type) if not func_details: LOG.warning('Resource type not yet implemented: %s' % resource_type) return LOG.debug('Deploying resource type "%s" id "%s"' % (resource_type, resource_id)) func_details = func_details[ACTION_CREATE] function = getattr(client, func_details['function']) params = func_details['parameters'] defaults = func_details.get('defaults', {}) if 'Properties' not in resource: resource['Properties'] = {} resource_props = resource['Properties'] if callable(params): params = params(resource_props, stack_name=stack_name, resources=resources) else: params = dict(params) for param_key, prop_keys in dict(params).items(): params.pop(param_key, None) if not isinstance(prop_keys, list): prop_keys = [prop_keys] for prop_key in prop_keys: if prop_key == PLACEHOLDER_RESOURCE_NAME: params[param_key] = resource_id resource_name = get_resource_name(resource) if resource_name: params[param_key] = resource_name else: # try to obtain physical resource name from stack resources try: return resolve_ref(stack_name, resource_id, resources, attribute='PhysicalResourceId') except Exception as e: LOG.debug( 'Unable to extract physical id for resource %s: %s' % (resource_id, e)) else: if callable(prop_key): prop_value = prop_key(resource_props, stack_name=stack_name, resources=resources) else: prop_value = resource_props.get(prop_key) if prop_value is not None: params[param_key] = prop_value # convert refs and boolean strings for param_key, prop_keys in dict(params).items(): tmp_value = params.get(param_key) if tmp_value is not None: params[param_key] = resolve_refs_recursively( stack_name, tmp_value, resources) # Convert to boolean (TODO: do this recursively?) if str(tmp_value).lower() in ['true', 'false']: params[param_key] = str(tmp_value).lower() == 'true' # convert any moto account IDs (123456789012) in ARNs to our format (000000000000) params = json.loads(aws_stack.fix_account_id_in_arns(json.dumps(params))) # assign default value if empty params = common.merge_recursive(defaults, params) # convert data types (e.g., boolean strings to bool) params = convert_data_types(func_details, params) # invoke function try: LOG.debug('Request for creating resource type "%s": %s %s' % (resource_type, func_details['function'], params)) result = function(**params) except Exception as e: LOG.warning('Error calling %s with params: %s for resource: %s' % (function, params, resource)) raise e # some resources have attached/nested resources which we need to create recursively now if resource_type == 'ApiGateway::Method': integration = resource_props.get('Integration') if integration: api_id = resolve_refs_recursively(stack_name, resource_props['RestApiId'], resources) res_id = resolve_refs_recursively(stack_name, resource_props['ResourceId'], resources) uri = integration.get('Uri') if uri: uri = resolve_refs_recursively(stack_name, uri, resources) aws_stack.connect_to_service('apigateway').put_integration( restApiId=api_id, resourceId=res_id, httpMethod=resource_props['HttpMethod'], type=integration['Type'], integrationHttpMethod=integration['IntegrationHttpMethod'], uri=uri) elif resource_type == 'SNS::Topic': subscriptions = resource_props.get('Subscription', []) for subscription in subscriptions: endpoint = resolve_refs_recursively(stack_name, subscription['Endpoint'], resources) topic_arn = retrieve_topic_arn(params['Name']) aws_stack.connect_to_service('sns').subscribe( TopicArn=topic_arn, Protocol=subscription['Protocol'], Endpoint=endpoint) elif resource_type == 'S3::Bucket': tags = resource_props.get('Tags') if tags: aws_stack.connect_to_service('s3').put_bucket_tagging( Bucket=params['Bucket'], Tagging={'TagSet': tags}) return result
def forward_request(self, method, path, data, headers): if method == 'OPTIONS': return 200 # check region try: aws_stack.check_valid_region(headers) except Exception as e: return make_error(message=str(e), code=400) if method == 'POST' and path == '/': # parse payload and extract fields req_data = urlparse.parse_qs(to_str(data)) req_action = req_data['Action'][0] topic_arn = req_data.get('TargetArn') or req_data.get('TopicArn') 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='Unable to find subscription for given ARN', code=400) content = '<Attributes>' for key, value in sub.items(): 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) 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': # No need to create a topic to send SMS with SNS # but we can't mock a sending so we only return that it went well if 'PhoneNumber' not in req_data: if topic_arn not in SNS_SUBSCRIPTIONS.keys(): return make_error(code=404, code_string='NotFound', message='Topic does not exist') publish_message(topic_arn, req_data) # return response here because we do not want the request to be forwarded to SNS backend return make_response(req_action) 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 == 'TagResource': tags = [] req_tags = {k: v for k, v in req_data.items() if k.startswith('Tags.member.')} for i in range(int(len(req_tags.keys()) / 2)): key = req_tags['Tags.member.' + str(i + 1) + '.Key'][0] value = req_tags['Tags.member.' + str(i + 1) + '.Value'][0] tags.append({'Key': key, 'Value': value}) do_tag_resource(topic_arn, tags) 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
def forward_request(self, method, path, data, headers): # check region try: aws_stack.check_valid_region(headers) except Exception as e: return make_error(message=str(e), code=400) if method == 'POST' and path == '/': # parse payload and extract fields req_data = urlparse.parse_qs(to_str(data)) req_action = req_data['Action'][0] topic_arn = req_data.get('TargetArn') or req_data.get('TopicArn') 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='Unable to find subscription for given ARN', code=400) content = '<Attributes>' for key, value in sub.items(): 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) 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': # No need to create a topic to send SMS with SNS # but we can't mock a sending so we only return that it went well if 'PhoneNumber' not in req_data: if topic_arn not in SNS_SUBSCRIPTIONS.keys(): return make_error(code=404, code_string='NotFound', message='Topic does not exist') publish_message(topic_arn, req_data) # return response here because we do not want the request to be forwarded to SNS backend return make_response(req_action) data = self._reset_account_id(data) return Request(data=data, headers=headers, method=method) return True
def forward_request(self, method, path, data, headers): if method == 'OPTIONS': return 200 data = data or '' data_orig = data data = aws_stack.fix_account_id_in_arns( data, existing='%3A{}%3Astack/'.format(TEST_AWS_ACCOUNT_ID), replace='%3A{}%3Astack/'.format(MOTO_CLOUDFORMATION_ACCOUNT_ID), colon_delimiter='') data = aws_stack.fix_account_id_in_arns( data, existing='%3A{}%3AchangeSet/'.format(TEST_AWS_ACCOUNT_ID), replace='%3A{}%3AchangeSet/'.format( MOTO_CLOUDFORMATION_ACCOUNT_ID), colon_delimiter='') data = aws_stack.fix_account_id_in_arns(data, existing=TEST_AWS_ACCOUNT_ID, replace=MOTO_ACCOUNT_ID, colon_delimiter='%3A') req_data = None if method == 'POST' and path == '/': req_data = urlparse.parse_qs(to_str(data)) req_data = dict([(k, v[0]) for k, v in req_data.items()]) action = req_data.get('Action') stack_name = req_data.get('StackName') if action == 'CreateStack': event_publisher.fire_event( event_publisher.EVENT_CLOUDFORMATION_CREATE_STACK, payload={'n': event_publisher.get_hash(stack_name)}) if action == 'DeleteStack': client = aws_stack.connect_to_service('cloudformation') stack_resources = client.list_stack_resources( StackName=stack_name)['StackResourceSummaries'] template_deployer.delete_stack(stack_name, stack_resources) if action == 'DescribeStackEvents': # fix an issue where moto cannot handle ARNs as stack names (or missing names) run_fix = not stack_name if stack_name: if stack_name.startswith('arn:aws:cloudformation'): run_fix = True pattern = r'arn:aws:cloudformation:[^:]+:[^:]+:stack/([^/]+)(/.+)?' stack_name = re.sub(pattern, r'\1', stack_name) if run_fix: stack_names = [ stack_name ] if stack_name else self._list_stack_names() client = aws_stack.connect_to_service('cloudformation') events = [] for stack_name in stack_names: tmp = client.describe_stack_events( StackName=stack_name)['StackEvents'][:1] events.extend(tmp) events = [{'member': e} for e in events] response_content = '<StackEvents>%s</StackEvents>' % obj_to_xml( events) return make_response('DescribeStackEvents', response_content) if req_data: if action == 'ValidateTemplate': return validate_template(req_data) if action in ['CreateStack', 'UpdateStack']: do_replace_url = is_real_s3_url(req_data.get('TemplateURL')) if do_replace_url: req_data['TemplateURL'] = convert_s3_to_local_url( req_data['TemplateURL']) url = req_data.get('TemplateURL', '') is_custom_local_endpoint = is_local_service_url( url) and '://localhost:' not in url modified_template_body = transform_template(req_data) if not modified_template_body and is_custom_local_endpoint: modified_template_body = get_template_body(req_data) if modified_template_body: req_data.pop('TemplateURL', None) req_data['TemplateBody'] = modified_template_body if modified_template_body or do_replace_url: data = urlparse.urlencode(req_data, doseq=True) return Request(data=data, headers=headers, method=method) if data != data_orig or action in [ 'DescribeChangeSet', 'ExecuteChangeSet' ]: return Request(data=urlparse.urlencode(req_data, doseq=True), headers=headers, method=method) return True
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 = urlparse.parse_qs(to_str(data), keep_blank_values=True) # parse data from query path if not req_data: parsed_path = urlparse.urlparse(path) req_data = urlparse.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) 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") 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 == "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