Ejemplo n.º 1
0
        private Task <TResp> SendRequest <TReq, TResp>(TReq request, CancellationToken token) where TResp : IResponse, new() where TReq : IRequest
        {
            request.Timestamp = DateTimeOffset.UtcNow;
            request.RequestId = Guid.NewGuid();

            _logger.LogTrace("Handling {RequestType} with request id {RequestId}", typeof(TReq).Name, request.RequestId);

            S3Config config        = _options.Value;
            Stream?  requestStream = _marshaller.MarshalRequest(request, config);

            _validator.ValidateAndThrow(request);

            StringBuilder sb = StringBuilderPool.Shared.Rent(200);

            RequestHelper.AppendScheme(sb, config);
            int schemeLength = sb.Length;

            RequestHelper.AppendHost(sb, config, request);

            request.SetHeader(HttpHeaders.Host, sb.ToString(schemeLength, sb.Length - schemeLength));
            request.SetHeader(AmzHeaders.XAmzDate, request.Timestamp, DateTimeFormat.Iso8601DateTime);

            if (requestStream != null)
            {
                foreach (IRequestStreamWrapper wrapper in _requestStreamWrappers)
                {
                    if (wrapper.IsSupported(request))
                    {
                        requestStream = wrapper.Wrap(requestStream, request);
                    }
                }
            }

            if (!request.Headers.TryGetValue(AmzHeaders.XAmzContentSha256, out string contentHash))
            {
                if (config.PayloadSignatureMode == SignatureMode.Unsigned)
                {
                    contentHash = "UNSIGNED-PAYLOAD";
                }
                else
                {
                    contentHash = requestStream == null ? "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" : CryptoHelper.Sha256Hash(requestStream, true).HexEncode();
                }

                request.SetHeader(AmzHeaders.XAmzContentSha256, contentHash);
            }

            _logger.LogDebug("ContentSha256 is {ContentSha256}", contentHash);

            //We add the authorization header here because we need ALL other headers to be present when we do
            _authBuilder.BuildAuthorization(request);

            RequestHelper.AppendUrl(sb, config, request);
            RequestHelper.AppendQueryParameters(sb, request);
            string url = sb.ToString();

            StringBuilderPool.Shared.Return(sb);

            return(HandleResponse <TReq, TResp>(request, url, requestStream, token));
        }
Ejemplo n.º 2
0
        public DefaultRequestHandler(IOptions <S3Config> options, IValidatorFactory validator, IMarshalFactory marshaller, IPostMapperFactory postMapper, INetworkDriver networkDriver, HeaderAuthorizationBuilder authBuilder, ILogger <DefaultRequestHandler> logger, IEnumerable <IRequestStreamWrapper>?requestStreamWrappers = null)
        {
            Validator.RequireNotNull(options, nameof(options));
            Validator.RequireNotNull(validator, nameof(validator));
            Validator.RequireNotNull(marshaller, nameof(marshaller));
            Validator.RequireNotNull(networkDriver, nameof(networkDriver));
            Validator.RequireNotNull(authBuilder, nameof(authBuilder));
            Validator.RequireNotNull(logger, nameof(logger));

            validator.ValidateAndThrow(options.Value);

            _validator     = validator;
            _options       = options;
            _networkDriver = networkDriver;
            _authBuilder   = authBuilder;
            _marshaller    = marshaller;
            _postMapper    = postMapper;
            _logger        = logger;

            if (requestStreamWrappers == null)
            {
                _requestStreamWrappers = Array.Empty <IRequestStreamWrapper>();
            }
            else
            {
                _requestStreamWrappers = requestStreamWrappers.ToList();
            }
        }
Ejemplo n.º 3
0
        public string SignRequest <TReq>(TReq request, TimeSpan expiresIn) where TReq : IRequest
        {
            request.Timestamp = DateTimeOffset.UtcNow;
            request.RequestId = Guid.NewGuid();

            _logger.LogTrace("Handling {RequestType} with request id {RequestId}", typeof(TReq).Name, request.RequestId);

            S3Config config = _options.Value;

            _marshaller.MarshalRequest(request, config);

            _validator.ValidateAndThrow(request);

            StringBuilder sb = StringBuilderPool.Shared.Rent(200);

            RequestHelper.AppendScheme(sb, config);
            int schemeLength = sb.Length;

            RequestHelper.AppendHost(sb, config, request);

            request.SetHeader(HttpHeaders.Host, sb.ToString(schemeLength, sb.Length - schemeLength));

            string scope = _scopeBuilder.CreateScope("s3", request.Timestamp);

            request.SetQueryParameter(AmzParameters.XAmzAlgorithm, SigningConstants.AlgorithmTag);
            request.SetQueryParameter(AmzParameters.XAmzCredential, _options.Value.Credentials.KeyId + '/' + scope);
            request.SetQueryParameter(AmzParameters.XAmzDate, request.Timestamp.ToString(DateTimeFormats.Iso8601DateTime, DateTimeFormatInfo.InvariantInfo));
            request.SetQueryParameter(AmzParameters.XAmzExpires, expiresIn.TotalSeconds.ToString(NumberFormatInfo.InvariantInfo));
            request.SetQueryParameter(AmzParameters.XAmzSignedHeaders, string.Join(";", SigningConstants.FilterHeaders(request.Headers).Select(x => x.Key)));

            //Copy all headers to query parameters
            foreach (KeyValuePair <string, string> header in request.Headers)
            {
                if (header.Key == HttpHeaders.Host)
                {
                    continue;
                }

                request.SetQueryParameter(header.Key, header.Value);
            }

            _authBuilder.BuildAuthorization(request);

            //Clear sensitive material from the request
            if (request is IContainSensitiveMaterial sensitive)
            {
                sensitive.ClearSensitiveMaterial();
            }

            RequestHelper.AppendUrl(sb, config, request);
            RequestHelper.AppendQueryParameters(sb, request);

            string url = sb.ToString();

            StringBuilderPool.Shared.Return(sb);
            return(url);
        }
Ejemplo n.º 4
0
        public DefaultSignedRequestHandler(IOptions <S3Config> options, IScopeBuilder scopeBuilder, IValidatorFactory validator, IMarshalFactory marshaller, QueryParameterAuthorizationBuilder authBuilder, ILogger <DefaultSignedRequestHandler> logger)
        {
            Validator.RequireNotNull(options, nameof(options));
            Validator.RequireNotNull(validator, nameof(validator));
            Validator.RequireNotNull(marshaller, nameof(marshaller));
            Validator.RequireNotNull(authBuilder, nameof(authBuilder));
            Validator.RequireNotNull(logger, nameof(logger));

            validator.ValidateAndThrow(options.Value);

            _validator    = validator;
            _options      = options;
            _authBuilder  = authBuilder;
            _marshaller   = marshaller;
            _logger       = logger;
            _scopeBuilder = scopeBuilder;
        }
Ejemplo n.º 5
0
        public DefaultRequestHandler(IOptions <S3Config> options, IValidatorFactory validator, IMarshalFactory marshaller, INetworkDriver networkDriver, IAuthorizationBuilder authBuilder, IEnumerable <IRequestStreamWrapper> requestStreamWrappers, ILogger <DefaultRequestHandler> logger)
        {
            Validator.RequireNotNull(options);
            Validator.RequireNotNull(validator);
            Validator.RequireNotNull(marshaller);
            Validator.RequireNotNull(networkDriver);
            Validator.RequireNotNull(authBuilder);
            Validator.RequireNotNull(requestStreamWrappers);
            Validator.RequireNotNull(logger);

            validator.ValidateAndThrow(options.Value);

            _validator             = validator;
            _options               = options;
            _networkDriver         = networkDriver;
            _authBuilder           = authBuilder;
            _marshaller            = marshaller;
            _requestStreamWrappers = requestStreamWrappers.ToList();
            _logger = logger;
        }
Ejemplo n.º 6
0
        public async Task <TResp> SendRequestAsync <TReq, TResp>(TReq request, CancellationToken cancellationToken = default) where TResp : IResponse, new() where TReq : IRequest
        {
            cancellationToken.ThrowIfCancellationRequested();

            request.Timestamp = DateTimeOffset.UtcNow;
            request.RequestId = Guid.NewGuid();

            _logger.LogTrace("Sending {RequestType} with request id {RequestId}", typeof(TReq).Name, request.RequestId);

            S3Config config = _options.Value;

            Stream requestStream = _marshaller.MarshalRequest(request, config);

            _validator.ValidateAndThrow(request);

            string bucketName = null;

            if (request is IHasBucketName bn)
            {
                bucketName = bn.BucketName;
            }

            string objectKey = null;

            if (request is IHasObjectKey ok)
            {
                objectKey = ok.ObjectKey;
            }

            //Ensure that the object key is encoded
            string encodedResource = objectKey != null?UrlHelper.UrlPathEncode(objectKey) : null;

            if (config.Endpoint == null || config.NamingMode == NamingMode.PathStyle)
            {
                if (bucketName != null)
                {
                    objectKey = bucketName + '/' + encodedResource;
                }
                else
                {
                    objectKey = encodedResource;
                }
            }
            else
            {
                objectKey = encodedResource;
            }

            StringBuilder sb = StringBuilderPool.Shared.Rent(100);

            Uri endpoint = config.Endpoint;

            if (endpoint != null)
            {
                sb.Append(endpoint.Host);

                if (!endpoint.IsDefaultPort)
                {
                    sb.Append(':').Append(endpoint.Port);
                }
            }
            else
            {
                if (config.NamingMode == NamingMode.VirtualHost)
                {
                    if (bucketName != null)
                    {
                        sb.Append(bucketName).Append(".s3.").Append(ValueHelper.EnumToString(config.Region)).Append(".amazonaws.com");
                    }
                    else
                    {
                        sb.Append("s3.").Append(ValueHelper.EnumToString(config.Region)).Append(".amazonaws.com");
                    }
                }
                else
                {
                    sb.Append("s3.").Append(ValueHelper.EnumToString(config.Region)).Append(".amazonaws.com");
                }
            }

            request.SetHeader(HttpHeaders.Host, sb.ToString());
            request.SetHeader(AmzHeaders.XAmzDate, request.Timestamp, DateTimeFormat.Iso8601DateTime);

            if (requestStream != null)
            {
                foreach (IRequestStreamWrapper wrapper in _requestStreamWrappers)
                {
                    if (wrapper.IsSupported(request))
                    {
                        requestStream = wrapper.Wrap(requestStream, request);
                    }
                }
            }

            if (!request.Headers.TryGetValue(AmzHeaders.XAmzContentSha256, out string contentHash))
            {
                if (config.PayloadSignatureMode == SignatureMode.Unsigned)
                {
                    contentHash = "UNSIGNED-PAYLOAD";
                }
                else
                {
                    contentHash = requestStream == null ? "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" : CryptoHelper.Sha256Hash(requestStream, true).HexEncode();
                }

                request.SetHeader(AmzHeaders.XAmzContentSha256, contentHash);
            }

            _logger.LogDebug("ContentSha256 is {ContentSha256}", contentHash);

            //We add the authorization header here because we need ALL other headers to be present when we do
            request.SetHeader(HttpHeaders.Authorization, _authBuilder.BuildAuthorization(request));

            sb.Append('/').Append(objectKey);

            //Map all the parameters on to the url
            if (request.QueryParameters.Count > 0)
            {
                sb.Append('?').Append(UrlHelper.CreateQueryString(request.QueryParameters));
            }

            string scheme  = endpoint == null ? config.UseTLS ? "https" : "http" : endpoint.Scheme;
            string fullUrl = scheme + "://" + sb;

            StringBuilderPool.Shared.Return(sb);

            _logger.LogDebug("Building request for {Url}", fullUrl);

            (int statusCode, IDictionary <string, string> headers, Stream responseStream) = await _networkDriver.SendRequestAsync(request.Method, fullUrl, request.Headers, requestStream, cancellationToken).ConfigureAwait(false);

            //Clear sensitive material from the request
            if (request is IContainSensitiveMaterial sensitive)
            {
                sensitive.ClearSensitiveMaterial();
            }

            TResp response = new TResp();

            response.StatusCode       = statusCode;
            response.ContentLength    = headers.GetHeaderLong(HttpHeaders.ContentLength);
            response.ConnectionClosed = "closed".Equals(headers.GetHeader(HttpHeaders.Connection), StringComparison.OrdinalIgnoreCase);
            response.Date             = headers.GetHeaderDate(HttpHeaders.Date, DateTimeFormat.Rfc1123);
            response.Server           = headers.GetHeader(HttpHeaders.Server);
            response.ResponseId       = headers.GetHeader(AmzHeaders.XAmzId2);
            response.RequestId        = headers.GetHeader(AmzHeaders.XAmzRequestId);

            // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
            response.IsSuccess = !(statusCode == 403 || //Forbidden
                                   statusCode == 400 || //BadRequest
                                   statusCode == 500 || //InternalServerError
                                   statusCode == 416 || //RequestedRangeNotSatisfiable
                                   statusCode == 405 || //MethodNotAllowed
                                   statusCode == 411 || //LengthRequired
                                   statusCode == 404 || //NotFound
                                   statusCode == 501 || //NotImplemented
                                   statusCode == 504 || //GatewayTimeout
                                   statusCode == 301 || //MovedPermanently
                                   statusCode == 412 || //PreconditionFailed
                                   statusCode == 307 || //TemporaryRedirect
                                   statusCode == 409 || //Conflict
                                   statusCode == 503);    //ServiceUnavailable

            //Only marshal successful responses
            if (response.IsSuccess)
            {
                _marshaller.MarshalResponse(config, request, response, headers, responseStream);
            }
            else
            {
                MemoryStream ms = new MemoryStream();
                responseStream.CopyTo(ms);

                if (ms.Length > 0)
                {
                    ms.Seek(0, SeekOrigin.Begin);

                    using (responseStream)
                        response.Error = ErrorHandler.Create(ms);

                    _logger.LogError("Received error: '{Message}'. Details: '{Details}'", response.Error.Message, response.Error.GetExtraData());
                }
            }

            return(response);
        }
Ejemplo n.º 7
0
        public async Task <TResp> SendRequestAsync <TReq, TResp>(TReq request, CancellationToken cancellationToken) where TResp : IResponse, new() where TReq : IRequest
        {
            _logger.LogTrace($"Sending {typeof(TReq)} to bucket '{request.BucketName}' as resource '{request.Resource}'");

            Stream requestStream = _marshaller.MarshalRequest(request);

            _validator.ValidateAndThrow(request);

            //Ensure that the resource is encoded
            string encodedResource = UrlHelper.UrlPathEncode(request.Resource);

            if (_options.Value.Endpoint == null || _options.Value.NamingType == NamingType.PathStyle)
            {
                if (!string.IsNullOrEmpty(request.BucketName))
                {
                    request.Resource = request.BucketName + '/' + encodedResource;
                }
                else
                {
                    request.Resource = encodedResource;
                }
            }
            else
            {
                request.Resource = encodedResource;
            }

            StringBuilder sb = new StringBuilder(512);

            if (_options.Value.Endpoint != null)
            {
                sb.Append(_options.Value.Endpoint.Host);
            }
            else
            {
                if (_options.Value.NamingType == NamingType.VirtualHost)
                {
                    if (!string.IsNullOrEmpty(request.BucketName))
                    {
                        sb.Append(request.BucketName).Append(".s3.").Append(ValueHelper.EnumToString(_options.Value.Region)).Append(".amazonaws.com");
                    }
                    else
                    {
                        sb.Append("s3.").Append(ValueHelper.EnumToString(_options.Value.Region)).Append(".amazonaws.com");
                    }
                }
                else
                {
                    sb.Append("s3.").Append(ValueHelper.EnumToString(_options.Value.Region)).Append(".amazonaws.com");
                }
            }

            request.AddHeader(HttpHeaders.Host, sb.ToString());
            request.AddHeader(AmzHeaders.XAmzDate, request.Date, DateTimeFormat.Iso8601DateTime);

            if (requestStream != null && _requestStreamWrappers != null)
            {
                foreach (IRequestStreamWrapper wrapper in _requestStreamWrappers)
                {
                    if (wrapper.IsSupported(request))
                    {
                        requestStream = wrapper.Wrap(requestStream, request);
                    }
                }
            }

            //We check if it was already added here because it might have been due to streaming support
            if (!request.Headers.ContainsKey(AmzHeaders.XAmzContentSha256))
            {
                string contentHash;

                if (!_options.Value.EnablePayloadSigning)
                {
                    contentHash = "UNSIGNED-PAYLOAD";
                }
                else
                {
                    contentHash = requestStream == null ? "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" : CryptoHelper.Sha256Hash(requestStream, true).HexEncode();
                }

                _logger.LogDebug("ContentSha256 is {ContentSha256}", contentHash);

                request.AddHeader(AmzHeaders.XAmzContentSha256, contentHash);
            }

            //We add the authorization header here because we need ALL other headers to be present when we do
            request.AddHeader(HttpHeaders.Authorization, _authBuilder.BuildAuthorization(request));

            sb.Append('/').Append(request.Resource);

            //Map all the parameters on to the url
            if (request.QueryParameters.Count > 0)
            {
                sb.Append('?').Append(UrlHelper.CreateQueryString(request.QueryParameters));
            }

            string fullUrl = "https://" + sb;

            _logger.LogDebug("Building request for {Url}", fullUrl);

            (int statusCode, IDictionary <string, string> headers, Stream responseStream) = await _networkDriver.SendRequestAsync(request.Method, fullUrl, request.Headers, requestStream, cancellationToken).ConfigureAwait(false);

            //Clear sensitive material from the request
            if (request is IContainSensitiveMaterial sensitive)
            {
                sensitive.ClearSensitiveMaterial();
            }

            TResp response = new TResp();

            response.StatusCode       = statusCode;
            response.ContentLength    = headers.GetHeaderLong(HttpHeaders.ContentLength);
            response.ConnectionClosed = "closed".Equals(headers.GetHeader(HttpHeaders.Connection), StringComparison.OrdinalIgnoreCase);
            response.Date             = headers.GetHeaderDate(HttpHeaders.Date, DateTimeFormat.Rfc1123);
            response.Server           = headers.GetHeader(HttpHeaders.Server);
            response.ResponseId       = headers.GetHeader(AmzHeaders.XAmzId2);
            response.RequestId        = headers.GetHeader(AmzHeaders.XAmzRequestId);

            // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
            response.IsSuccess = !(statusCode == 403 || //Forbidden
                                   statusCode == 400 || //BadRequest
                                   statusCode == 500 || //InternalServerError
                                   statusCode == 416 || //RequestedRangeNotSatisfiable
                                   statusCode == 405 || //MethodNotAllowed
                                   statusCode == 411 || //LengthRequired
                                   statusCode == 404 || //NotFound
                                   statusCode == 501 || //NotImplemented
                                   statusCode == 504 || //GatewayTimeout
                                   statusCode == 301 || //MovedPermanently
                                   statusCode == 412 || //PreconditionFailed
                                   statusCode == 307 || //TemporaryRedirect
                                   statusCode == 409 || //Conflict
                                   statusCode == 503);    //ServiceUnavailable

            //Don't know why, but ContentLength seems to be 0 sometimes, even though it is in the headers
            if (!response.IsSuccess && (response.ContentLength > 0 || responseStream.Length > 0))
            {
                using (responseStream)
                    response.Error = ErrorHandler.Create(responseStream);

                _logger.LogError("Received error: '{Message}'. Details: '{Details}'", response.Error.Message, response.Error.GetExtraData());
            }

            //Only marshal successful responses
            if (response.IsSuccess)
            {
                _marshaller.MarshalResponse(request, response, headers, responseStream);
            }

            return(response);
        }