aws / aws-sdk-net

The official AWS SDK for .NET. For more information on the AWS SDK for .NET, see our web site:
http://aws.amazon.com/sdkfornet/
Apache License 2.0
2.05k stars 852 forks source link

ListContactsRequestMarshaller incorrectly builds content byte array #1793

Closed franhoey closed 6 months ago

franhoey commented 3 years ago

Description

Calling ListContactsAsync always errors with the message "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."

The signature seems to be throw by the Request.Content object build in the ListContactsRequestMarshaller. By building my code against the source project and setting request.Content to null the call succeeds.

Reproduction Steps

Setup a Contact List Add a Contact to the list Call ListContactsAsync to retrieve contacts

Logs

N/A

Environment

Resolution

Sorry, I'm can see any obvious issue with the content. For example even just "{}" causes a problem


This is a :bug: bug-report

franhoey commented 3 years ago

Forgot to add, there is a forum post open about this - https://forums.aws.amazon.com/thread.jspa?messageID=971818

ashishdhingra commented 3 years ago

Hi @franhoey,

Good afternoon.

Could you please share the following to reproduce the issue:

Not sure if this is related to SigV4 signing, following posts might be helpful:

As 2nd link mentions, it might also occur if you set UseHttp as true in client config.

Thanks, Ashish

franhoey commented 3 years ago

Reduced class attached. The CreateContactAsync function works, but the GetJobSubscribersAsync doesn't work.

I have tried setting UseHttp to false, but it didn't make a difference.

Commenting out this section of ListContactsRequestMarshaller fixes the issue

using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture))
            {
                JsonWriter writer = new JsonWriter(stringWriter);
                writer.WriteObjectStart();
                var context = new JsonMarshallerContext(request, writer);
                if (publicRequest.IsSetFilter())
                {
                    context.Writer.WritePropertyName("Filter");
                    context.Writer.WriteObjectStart();
                    var marshaller = ListContactsFilterMarshaller.Instance;
                    marshaller.Marshall(publicRequest.Filter, context);
                    context.Writer.WriteObjectEnd();
                }
                writer.WriteObjectEnd();
                string snippet = stringWriter.ToString();
                request.Content = System.Text.Encoding.UTF8.GetBytes(snippet);
            }
ashishdhingra commented 3 years ago

Easily reproducible using the following code (used AWSSDK.SimpleEmailV2 version 3.5.2.6):

using System;
using System.Threading;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.SimpleEmailV2;
using Amazon.SimpleEmailV2.Model;

namespace SimpleEmailService_Issue1793
{
    class Program
    {
        private const string CONTACT_LIST_NAME = "ChesterDevJobs";
        private const string INSTANT_ALERT_TOPIC = "InstantJobAlerts";
        private static readonly AWSCredentials _credentials;
        private static readonly AmazonSimpleEmailServiceV2Config _config;

        static Program()
        {
            var chain = new CredentialProfileStoreChain();
            if (!chain.TryGetAWSCredentials("default", out _credentials))
            {
                throw new ArgumentNullException("Unable to load credentials in default profile chain");
            }

            _config = new AmazonSimpleEmailServiceV2Config()
            {
                RegionEndpoint = RegionEndpoint.EUWest2,
            };
        }

        static void Main(string[] args)
        {
            //var createContactListResult = CreateContactListAsync(CONTACT_LIST_NAME).Result;
            //var createContactResult = CreateContactAsync("testcontactemail@domain.com", new CancellationToken()).Result;
            var result = GetJobSubscribersAsync(null, new CancellationToken()).Result;
        }

        public static async Task<CreateContactListResponse> CreateContactListAsync(string listName)
        {
            using (var client = new AmazonSimpleEmailServiceV2Client(_credentials, _config))
            {
                return await client.CreateContactListAsync(
                    new CreateContactListRequest()
                    {
                         ContactListName = CONTACT_LIST_NAME,
                         Topics = new System.Collections.Generic.List<Topic> (){ new Topic() { TopicName = INSTANT_ALERT_TOPIC, DisplayName = INSTANT_ALERT_TOPIC, DefaultSubscriptionStatus = SubscriptionStatus.OPT_IN } }
                    });
            }
        }

        public static async Task<CreateContactResponse> CreateContactAsync(string emailAddress, CancellationToken cancellationToken)
        {
            using (var client = new AmazonSimpleEmailServiceV2Client(_credentials, _config))
            {
                return await client.CreateContactAsync(
                    new CreateContactRequest()
                    {
                        ContactListName = CONTACT_LIST_NAME,
                        EmailAddress = emailAddress
                    },
                    cancellationToken);
            }
        }

        public static async Task<ListContactsResponse> GetJobSubscribersAsync(string nextToken, CancellationToken cancellationToken)
        {
            using (var client = new AmazonSimpleEmailServiceV2Client(_credentials, _config))
            {
                return await client.ListContactsAsync(
                    new ListContactsRequest()
                    {
                        ContactListName = CONTACT_LIST_NAME,
                        PageSize = 1,
                        Filter = new ListContactsFilter()
                        {
                            TopicFilter = new TopicFilter()
                            { TopicName = INSTANT_ALERT_TOPIC, UseDefaultIfPreferenceUnavailable = true },
                            FilteredStatus = SubscriptionStatus.OPT_IN
                        }
                    },
                    cancellationToken);
            }
        }
    }
}

NOTE: The ContentType of Response (Amazon.Runtime.Internal.Transform.HttpClientResponseData) in the innermost exception is application/x-amz-json-1.1.

The below AWS CLI command works fine:

aws sesv2 list-contacts --contact-list-name "ChesterDevJobs" --region eu-west-2 --page-size 1 --filter '{"FilteredStatus": "OPT_IN", "TopicFilter": {"TopicName": "InstantJobAlerts", "UseDefaultIfPreferenceUnavailable": true}}'
github-actions[bot] commented 2 years ago

We have noticed this issue has not received attention in 1 year. We will close this issue for now. If you think this is in error, please feel free to comment and reopen the issue.

ashishdhingra commented 2 years ago

No auto-close.

ashishdhingra commented 2 years ago

This is a cross SDK service issue tracked via https://github.com/aws/aws-sdk/issues/307.

ashishdhingra commented 6 months ago

Closing this in favor of https://github.com/aws/aws-sdk/issues/307. The related issue has a feature request item in service team backlog.

github-actions[bot] commented 6 months ago

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.