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
49 changes: 48 additions & 1 deletion src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
/// <summary>
/// Regex for Tenant ID validation.
/// </summary>
private static readonly Regex TenantIdValueRegex = new Regex(@"^[a-zA-Z0-9]*$");
private static readonly Regex TenantIdValueRegex = new Regex(@"^(?!.*\.\.)(?!\.$)[a-zA-Z0-9!._*'()\-\u005F]*$", RegexOptions.Compiled);

/// <summary>
/// RFC7230 token characters: letters, digits and these symbols: ! # $ % & ' * + - . ^ _ ` | ~

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 39 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'
/// </summary>
private static readonly Regex HeaderKeyRegEx = new Regex(@"^[A-Za-z0-9!#$%&'*+\-\.\^_`|~]+$", RegexOptions.Compiled);

/// <summary>
/// Initializes a new instance of the <see cref="BaseLokiHttpClient"/> class.
Expand Down Expand Up @@ -91,6 +96,48 @@
headers.Add(TenantHeader, tenant);
}

/// <summary>
/// Sets default headers for the HTTP client.
/// Existing headers with the same key will not be overwritten.
/// </summary>
/// <param name="defaultHeaders">A dictionary of headers to set as default.</param>
public virtual void SetDefaultHeaders(IDictionary<string, string> defaultHeaders)
{

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Check failure on line 105 in src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs

View workflow job for this annotation

GitHub Actions / windows-latest


if (defaultHeaders == null)
{
throw new ArgumentNullException(nameof(defaultHeaders), "Default headers cannot be null.");
}

foreach (var header in defaultHeaders)
{
if (string.IsNullOrWhiteSpace(header.Key))
{
throw new ArgumentException("Header name cannot be null, empty, or whitespace.", nameof(defaultHeaders));
}

if (!HeaderKeyRegEx.IsMatch(header.Key))
{
throw new ArgumentException($"Header name '{header.Key}' contains invalid characters.", nameof(defaultHeaders));
}

if (header.Value == null)
{
throw new ArgumentException($"Header value for '{header.Key}' cannot be null.", nameof(defaultHeaders));
}

if (header.Value.Length == 0)
{
throw new ArgumentException($"Header value for '{header.Key}' cannot be empty.", nameof(defaultHeaders));
}

if (!HttpClient.DefaultRequestHeaders.Contains(header.Key))
{
HttpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
}

/// <inheritdoc/>
public virtual void Dispose() => HttpClient.Dispose();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void HttpClientShouldBeCreatedIfNotProvider()
[Fact]
public void BasicAuthHeaderShouldBeCorrect()
{
var credentials = new LokiCredentials {Login = "Billy", Password = "Herrington"};
var credentials = new LokiCredentials { Login = "Billy", Password = "Herrington" };
using var client = new TestLokiHttpClient();

client.SetCredentials(credentials);
Expand All @@ -48,16 +48,96 @@ public void AuthorizationHeaderShouldNotBeSetWithoutCredentials()
client.Client.DefaultRequestHeaders.Authorization.ShouldBeNull();
}

[Fact]
public void TenantHeaderShouldBeCorrect()
[Theory]
[InlineData("tenant123", true)] // only alphanumeric
[InlineData("tenant-123", true)] // allowed hyphen
[InlineData("tenant..123", false)] // double period not allowed
[InlineData(".", false)] // single period not allowed
[InlineData("tenant!_*.123'()", true)] // allowed special characters
[InlineData("tenant-123...", false)] // ends with multiple periods
[InlineData("tenant123456...test", false)] // ends with period
[InlineData("tenant1234567890!@", false)] // '@' is not allowed
[InlineData("a", true)] // minimal length
[InlineData("tenant_with_underscores", true)] // underscores
[InlineData("tenant..", false)] // ends with double period
[InlineData("..tenant", false)] // starts with double period
[InlineData("tenant-.-test", true)] // single periods inside are ok
public void TenantHeaderShouldBeCorrect(string tenantId, bool isValid)
{
using var client = new TestLokiHttpClient();

if (isValid)
{
// Act
client.SetTenant(tenantId);

// Assert header is correctly set
var tenantHeaders = client.Client.DefaultRequestHeaders
.GetValues("X-Scope-OrgID")
.ToList();

tenantHeaders.ShouldBeEquivalentTo(new List<string> { tenantId });
}
else
{
// Act & Assert: invalid tenant IDs throw ArgumentException
Should.Throw<ArgumentException>(() => client.SetTenant(tenantId));
}
}

// Allowed special characters
[Theory]
[InlineData('!', true)]
[InlineData('.', true)]
[InlineData('_', true)]
[InlineData('*', true)]
[InlineData('\'', true)]
[InlineData('(', true)]
[InlineData(')', true)]
[InlineData('-', true)]

// Disallowed special characters
[InlineData('@', false)]
[InlineData('#', false)]
[InlineData('&', false)]
[InlineData('$', false)]
[InlineData('%', false)]
[InlineData('^', false)]
[InlineData('=', false)]
[InlineData('+', false)]
[InlineData('[', false)]
[InlineData(']', false)]
[InlineData('{', false)]
[InlineData('}', false)]
[InlineData('<', false)]
[InlineData('>', false)]
[InlineData('?', false)]
[InlineData('/', false)]
[InlineData('\\', false)]
[InlineData('|', false)]
[InlineData('~', false)]
[InlineData('"', false)]
public void TenantSpecialCharacterShouldValidateCorrectly(char specialChar, bool isValid)
{
var tenantId = "lokitenant";
using var client = new TestLokiHttpClient();
string tenantId = "tenant" + specialChar + "123";

client.SetTenant(tenantId);
if (isValid)
{
// Should succeed
client.SetTenant(tenantId);

var tenantHeaders = client.Client.DefaultRequestHeaders.GetValues("X-Scope-OrgID").ToList();
tenantHeaders.ShouldBeEquivalentTo(new List<string> {"lokitenant"});
var tenantHeaders = client.Client.DefaultRequestHeaders
.GetValues("X-Scope-OrgID")
.ToList();

tenantHeaders.ShouldBeEquivalentTo(new List<string> { tenantId });
}
else
{
// Should throw
Should.Throw<ArgumentException>(() => client.SetTenant(tenantId));
}
}

[Fact]
Expand All @@ -78,4 +158,110 @@ public void TenantHeaderShouldThrowAnExceptionOnTenantIdAgainstRule()

Should.Throw<ArgumentException>(() => client.SetTenant(tenantId));
}
}

[Theory]
[InlineData("Custom-Header", "HeaderValue", true)]
[InlineData("X-Test", "12345", true)]
[InlineData("X-Correlation-ID", "abcd-1234", true)]
[InlineData("X-Feature-Flag", "enabled", true)]
[InlineData("", "value", false)]
[InlineData(" ", "value", false)]
[InlineData(null, "value", false)]
[InlineData("Invalid Header", "value", false)]
[InlineData("X-Test", "", false)]
[InlineData("X-Test", null, false)]
public void SetDefaultHeadersShouldValidateCorrectly(string headerKey, string headerValue, bool isValid)
{
using var httpClient = new HttpClient();
var client = new TestLokiHttpClient(httpClient);

if (isValid)
{
var headersToSet = new Dictionary<string, string>
{
{ headerKey, headerValue }
};

client.SetDefaultHeaders(headersToSet);

httpClient.DefaultRequestHeaders.Contains(headerKey).ShouldBeTrue();
httpClient.DefaultRequestHeaders
.GetValues(headerKey)
.ShouldBe(new[] { headerValue });
}
else
{
Should.Throw<ArgumentException>(() =>
{
var headersToSet = new Dictionary<string, string>
{
{ headerKey, headerValue }
};
client.SetDefaultHeaders(headersToSet);
});
}
}

[Theory]
[InlineData('!', true)]
[InlineData('#', true)]
[InlineData('$', true)]
[InlineData('%', true)]
[InlineData('&', true)]
[InlineData('\'', true)]
[InlineData('*', true)]
[InlineData('+', true)]
[InlineData('-', true)]
[InlineData('.', true)]
[InlineData('^', true)]
[InlineData('_', true)]
[InlineData('`', true)]
[InlineData('|', true)]
[InlineData('~', true)]
[InlineData('A', true)]
[InlineData('z', true)]
[InlineData(' ', false)]
[InlineData('(', false)]
[InlineData(')', false)]
[InlineData('<', false)]
[InlineData('>', false)]
[InlineData('@', false)]
[InlineData(',', false)]
[InlineData(';', false)]
[InlineData(':', false)]
[InlineData('"', false)]
[InlineData('/', false)]
[InlineData('[', false)]
[InlineData(']', false)]
[InlineData('?', false)]
[InlineData('=', false)]
[InlineData('{', false)]
[InlineData('}', false)]
[InlineData('\\', false)]
[InlineData('\t', false)]
public void DefaultHeaderCharactersShouldValidateCorrectly(char character, bool isValid) // Valid token characters according to RFC 7230
{
using var httpClient = new HttpClient();
var client = new TestLokiHttpClient(httpClient);

string headerKey = "X-Test" + character;
var headersToSet = new Dictionary<string, string>
{
{ headerKey, "value" }
};

if (isValid)
{
// Should succeed
client.SetDefaultHeaders(headersToSet);

httpClient.DefaultRequestHeaders.Contains(headerKey).ShouldBeTrue();
httpClient.DefaultRequestHeaders.GetValues(headerKey).ShouldBe(new[] { "value" });
}
else
{
// Should throw exception
Should.Throw<ArgumentException>(() => client.SetDefaultHeaders(headersToSet));
}
}
}
Loading