diff --git a/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs b/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs index 44517179b..7b00b7ac3 100644 --- a/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs +++ b/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs @@ -6,8 +6,9 @@ namespace Ocelot.Cache; public class DefaultCacheKeyGenerator : ICacheKeyGenerator { public const char Delimiter = '-'; - - public async ValueTask GenerateRequestCacheKey(DownstreamRequest downstreamRequest, DownstreamRoute downstreamRoute) + + // TODO: Split the code into protected virtual methods for easier fine-tuning + public virtual async ValueTask GenerateRequestCacheKey(DownstreamRequest downstreamRequest, DownstreamRoute downstreamRoute) { var builder = new StringBuilder() .Append(downstreamRequest.Method) diff --git a/src/Ocelot/Cache/OutputCacheMiddleware.cs b/src/Ocelot/Cache/OutputCacheMiddleware.cs index 4eb02ae08..02c1702bc 100644 --- a/src/Ocelot/Cache/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/OutputCacheMiddleware.cs @@ -57,11 +57,17 @@ public async Task Invoke(HttpContext httpContext) } var downstreamResponse = httpContext.Items.DownstreamResponse(); - cached = await CreateCachedResponse(downstreamResponse); - - var ttl = TimeSpan.FromSeconds(options.TtlSeconds); - _outputCache.Add(downStreamRequestCacheKey, cached, options.Region, ttl); - Logger.LogDebug(() => $"Finished response added to cache for the '{downstreamUrlKey}' key."); + if (downstreamResponse.StatusCode == HttpStatusCode.OK) + { + cached = await CreateCachedResponse(downstreamResponse); + var ttl = TimeSpan.FromSeconds(options.TtlSeconds); + _outputCache.Add(downStreamRequestCacheKey, cached, options.Region, ttl); + Logger.LogDebug(() => $"Finished response added to cache for the '{downstreamUrlKey}' key."); + } + else + { + Logger.LogDebug($"HTTP request failed: could not create cache for the '{downstreamUrlKey}' key."); + } } private static void SetHttpResponseMessageThisRequest(HttpContext context, DownstreamResponse response) diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index 8437af39e..5582737f3 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -9,7 +9,7 @@ public class IPSecurityPolicy : ISecurityPolicy { public Response Security(DownstreamRoute downstreamRoute, HttpContext context) { - var clientIp = context.Connection.RemoteIpAddress; + var clientIp = context.GetClientIpAddress(); var options = downstreamRoute.SecurityOptions; if (options == null || clientIp == null) { @@ -18,7 +18,7 @@ public Response Security(DownstreamRoute downstreamRoute, HttpContext context) if (options.IPBlockedList?.Count > 0) { - if (options.IPBlockedList.Contains(clientIp.ToString())) + if (options.IPBlockedList.Contains(clientIp)) { var error = new UnauthenticatedError($"This request rejects access to {clientIp} IP"); return new ErrorResponse(error); @@ -27,7 +27,7 @@ public Response Security(DownstreamRoute downstreamRoute, HttpContext context) if (options.IPAllowedList?.Count > 0) { - if (!options.IPAllowedList.Contains(clientIp.ToString())) + if (!options.IPAllowedList.Contains(clientIp)) { var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid"); return new ErrorResponse(error); diff --git a/src/Ocelot/Security/SecurityPolicyExtensions.cs b/src/Ocelot/Security/SecurityPolicyExtensions.cs new file mode 100644 index 000000000..7b52f2061 --- /dev/null +++ b/src/Ocelot/Security/SecurityPolicyExtensions.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Ocelot.Security; + +public static class SecurityPolicyExtensions +{ + public static string GetClientIpAddress(this HttpContext httpContext, bool tryUseXForwardHeader = true) + { + if (httpContext == null) + { + return null; + } + + string ip = null; + + // X-Forwarded-For => Using the First entry in the list + if (string.IsNullOrWhiteSpace(ip) && tryUseXForwardHeader) + { + ip = httpContext.GetHeaderValue("X-Forwarded-For").SplitCsv().FirstOrDefault(); + } + + // RemoteIpAddress is always null in DNX RC1 Update1 (bug). + if (string.IsNullOrWhiteSpace(ip) && httpContext.Connection?.RemoteIpAddress != null) + { + ip = httpContext.Connection.RemoteIpAddress.ToString(); + } + + if (string.IsNullOrWhiteSpace(ip)) + { + ip = httpContext.GetHeaderValue("REMOTE_ADDR"); + } + + if (ip == "::1") + { + ip = "127.0.0.1"; + } + + return ip; + } + + public static string GetHeaderValue(this HttpContext httpContext, string headerName) + { + if (httpContext?.Request?.Headers?.TryGetValue(headerName, out StringValues values) ?? false) + { + return values.ToString(); + } + + return string.Empty; + } + + public static List SplitCsv(this string csvList, bool nullOrWhitespaceInputReturnsNull = false) + { + if (string.IsNullOrWhiteSpace(csvList)) + { + return nullOrWhitespaceInputReturnsNull ? null : new List(); + } + + return csvList + .TrimEnd(',') + .Split(',') + .AsEnumerable() + .Select(s => s.Trim()) + .ToList(); + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 97826bc0e..a164ae015 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -134,6 +134,7 @@ public async Task CreateHttpResponseMessage_CachedIsNull() // Arrange CachedResponse cached = null; GivenThereIsACachedResponse(cached); + GivenResponseIsNotCached(new HttpResponseMessage(HttpStatusCode.OK)); GivenTheDownstreamRouteIs(); // Act