Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/Ocelot/Cache/CacheKeyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ public class CacheKeyGenerator : ICacheKeyGenerator
{
public string GenerateRequestCacheKey(DownstreamRequest downstreamRequest)
{
var downStreamUrlKeyBuilder = new StringBuilder($"{downstreamRequest.Method}-{downstreamRequest.OriginalString}");
var builder = new StringBuilder($"{downstreamRequest.Method}-{downstreamRequest.OriginalString}");

if (downstreamRequest.Headers?.TryGetValues("Content-Language", out IEnumerable<string> values) ?? false)
{
var contentLanguage = values.Any()
? "-" + string.Join(string.Empty, values)
: string.Empty;
builder.Append(contentLanguage);
}

if (downstreamRequest.Content != null)
{
var requestContentString = Task.Run(async () => await downstreamRequest.Content.ReadAsStringAsync()).Result;
downStreamUrlKeyBuilder.Append(requestContentString);
var requestContentString = Task.Run(downstreamRequest.Content.ReadAsStringAsync).Result;
builder.Append(requestContentString);
}

var hashedContent = MD5Helper.GenerateMd5(downStreamUrlKeyBuilder.ToString());
return hashedContent;
return MD5Helper.GenerateMd5(builder.ToString());
}
}
}
18 changes: 11 additions & 7 deletions src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,19 @@ public async Task Invoke(HttpContext httpContext)

var downstreamResponse = httpContext.Items.DownstreamResponse();

cached = await CreateCachedResponse(downstreamResponse);

_outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(downstreamRoute.CacheOptions.TtlSeconds), downstreamRoute.CacheOptions.Region);

Logger.LogDebug($"Finished response added to cache for the '{downstreamUrlKey}' key.");
if (downstreamResponse.StatusCode == HttpStatusCode.OK)
{
cached = await CreateCachedResponse(downstreamResponse);
_outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(downstreamRoute.CacheOptions.TtlSeconds), downstreamRoute.CacheOptions.Region);
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)
private static void SetHttpResponseMessageThisRequest(HttpContext context, DownstreamResponse response)
{
context.Items.UpsertDownstreamResponse(response);
}
Expand Down
6 changes: 3 additions & 3 deletions src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class IPSecurityPolicy : ISecurityPolicy
{
public async Task<Response> Security(DownstreamRoute downstreamRoute, HttpContext httpContext)
{
var clientIp = httpContext.Connection.RemoteIpAddress;
var clientIp = httpContext.GetClientIpAddress();
var securityOptions = downstreamRoute.SecurityOptions;
if (securityOptions == null)
{
Expand All @@ -18,7 +18,7 @@ public async Task<Response> Security(DownstreamRoute downstreamRoute, HttpContex

if (securityOptions.IPBlockedList != null)
{
if (securityOptions.IPBlockedList.Exists(f => f == clientIp.ToString()))
if (securityOptions.IPBlockedList.Exists(f => f == clientIp))
{
var error = new UnauthenticatedError($" This request rejects access to {clientIp} IP");
return new ErrorResponse(error);
Expand All @@ -27,7 +27,7 @@ public async Task<Response> Security(DownstreamRoute downstreamRoute, HttpContex

if (securityOptions.IPAllowedList?.Count > 0)
{
if (!securityOptions.IPAllowedList.Exists(f => f == clientIp.ToString()))
if (!securityOptions.IPAllowedList.Exists(f => f == clientIp))
{
var error = new UnauthenticatedError($"{clientIp} does not allow access, the request is invalid");
return new ErrorResponse(error);
Expand Down
66 changes: 66 additions & 0 deletions src/Ocelot/Security/SecurityPolicyExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<string> SplitCsv(this string csvList, bool nullOrWhitespaceInputReturnsNull = false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be private

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...till the moment the author will write at least one unit test for this method. 🤣

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless no one uses it outside of this class

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static List<string> SplitCsv(this string csvList, bool nullOrWhitespaceInputReturnsNull = false)
private static IReadOnlyCollection<string> SplitCsv(this string csvList, bool nullOrWhitespaceInputReturnsNull = false)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensuring about strong safety by IReadOnlyCollection and making the method private don't give much outcome by having it as read-only.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method is only use in this class, so, it must be private. There will always be time to make it public, if necessary

{
if (string.IsNullOrWhiteSpace(csvList))
{
return nullOrWhitespaceInputReturnsNull ? null : new List<string>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nullOrWhitespaceInputReturnsNull ? null : new List<string>();
return nullOrWhitespaceInputReturnsNull ? null : Array.Empty<string>();

}

return csvList
.TrimEnd(',')
.Split(',')
.AsEnumerable()
.Select(s => s.Trim())
.ToList();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.ToList();
.ToArray();

Copy link
Member

@raman-m raman-m Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RaynaldM
Why to array?
The author just wants to match the returning type which is List<string>😄
Are you kidding? 😉 He will get compilation error. 🤣

Copy link
Collaborator

@RaynaldM RaynaldM Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if @nurhat applies all these changes no problems compiling.

I always recommend the use of Single-Dimensional Array because it is the most sober in memory and cpu.
If we don't need more functions, like in lists and other dictionaries, then let's use the simpler and more sober one

}
}