diff --git a/Directory.Build.props b/Directory.Build.props
index e04826178..70c21f5e4 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,7 +9,7 @@
$(Version)
$(Version)
Testcontainers
- Copyright (c) 2019 - 2023 Andre Hofmeister and other authors
+ Copyright (c) 2019 - 2024 Andre Hofmeister and other authors
Andre Hofmeister and contributors
Andre Hofmeister
Testcontainers for .NET is a library to support tests with throwaway instances of Docker containers for all compatible .NET Standard versions.
diff --git a/LICENSE b/LICENSE
index 83918c3a7..4ebf42b2d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2019 - 2023 Andre Hofmeister and other authors
+Copyright (c) 2019 - 2024 Andre Hofmeister and other authors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/README.md b/README.md
index 6c59acf34..b27fd6184 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ See [LICENSE](https://github.com/testcontainers/testcontainers-dotnet/blob/main/
## Copyright
-Copyright (c) 2019 - 2023 Andre Hofmeister and other authors.
+Copyright (c) 2019 - 2024 Andre Hofmeister and other authors.
See [contributors][testcontainers-dotnet-contributors] for all contributors.
diff --git a/docs/index.md b/docs/index.md
index 593b526f6..135e8c274 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -88,7 +88,7 @@ See [LICENSE](https://raw.githubusercontent.com/testcontainers/testcontainers-do
## Copyright
-Copyright (c) 2019 - 2023 Andre Hofmeister and other authors.
+Copyright (c) 2019 - 2024 Andre Hofmeister and other authors.
See [contributors][testcontainers-dotnet-contributors] for all contributors.
diff --git a/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj b/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj
index 4ef9bff5f..e16b4437c 100644
--- a/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj
+++ b/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj
@@ -2,7 +2,7 @@
netstandard2.0;netstandard2.1
latest
- Copyright (c) 2019 - 2023 Liam Wilson, Andre Hofmeister and other authors
+ Copyright (c) 2019 - 2024 Liam Wilson, Andre Hofmeister and other authors
Liam Wilson, Andre Hofmeister and contributors
A Testcontainers Papercut module for testing SMTP clients and sending emails.
diff --git a/src/Testcontainers/Builders/Base64Provider.cs b/src/Testcontainers/Builders/Base64Provider.cs
index fe71dddba..7998aa5e8 100644
--- a/src/Testcontainers/Builders/Base64Provider.cs
+++ b/src/Testcontainers/Builders/Base64Provider.cs
@@ -72,16 +72,37 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
return null;
}
- if (string.IsNullOrEmpty(auth.GetString()))
+ if (!JsonValueKind.String.Equals(auth.ValueKind) && !JsonValueKind.Null.Equals(auth.ValueKind))
{
+ _logger.DockerRegistryAuthPropertyValueKindInvalid(hostname, auth.ValueKind);
return null;
}
- var credentialInBytes = Convert.FromBase64String(auth.GetString());
- var credential = Encoding.UTF8.GetString(credentialInBytes).Split(new[] { ':' }, 2);
+ var authValue = auth.GetString();
+
+ if (string.IsNullOrEmpty(authValue))
+ {
+ _logger.DockerRegistryAuthPropertyValueNotFound(hostname);
+ return null;
+ }
+
+ byte[] credentialInBytes;
+
+ try
+ {
+ credentialInBytes = Convert.FromBase64String(authValue);
+ }
+ catch (FormatException e)
+ {
+ _logger.DockerRegistryAuthPropertyValueInvalidBase64(hostname, e);
+ return null;
+ }
+
+ var credential = Encoding.Default.GetString(credentialInBytes).Split(new[] { ':' }, 2);
if (credential.Length != 2)
{
+ _logger.DockerRegistryAuthPropertyValueInvalidBasicAuthenticationFormat(hostname);
return null;
}
diff --git a/src/Testcontainers/Logging.cs b/src/Testcontainers/Logging.cs
index c9bfddf90..18f5d2ae0 100644
--- a/src/Testcontainers/Logging.cs
+++ b/src/Testcontainers/Logging.cs
@@ -2,6 +2,7 @@ namespace DotNet.Testcontainers
{
using System;
using System.Collections.Generic;
+ using System.Text.Json;
using System.Text.RegularExpressions;
using DotNet.Testcontainers.Images;
using Microsoft.Extensions.Logging;
@@ -82,6 +83,18 @@ private static readonly Action _DockerConfigFileNotF
private static readonly Action _SearchingDockerRegistryCredential
= LoggerMessage.Define(LogLevel.Information, default, "Searching Docker registry credential in {CredentialStore}");
+ private static readonly Action _DockerRegistryAuthPropertyValueKindInvalid
+ = LoggerMessage.Define(LogLevel.Warning, default, "The \"auth\" property value kind for {DockerRegistry} is invalid: {ValueKind}");
+
+ private static readonly Action _DockerRegistryAuthPropertyValueNotFound
+ = LoggerMessage.Define(LogLevel.Warning, default, "The \"auth\" property value for {DockerRegistry} not found");
+
+ private static readonly Action _DockerRegistryAuthPropertyValueInvalidBase64
+ = LoggerMessage.Define(LogLevel.Warning, default, "The \"auth\" property value for {DockerRegistry} is not a valid Base64 string");
+
+ private static readonly Action _DockerRegistryAuthPropertyValueInvalidBasicAuthenticationFormat
+ = LoggerMessage.Define(LogLevel.Warning, default, "The \"auth\" property value for {DockerRegistry} should contain one colon separating the username and the password (basic authentication)");
+
private static readonly Action _DockerRegistryCredentialNotFound
= LoggerMessage.Define(LogLevel.Information, default, "Docker registry credential {DockerRegistry} not found");
@@ -212,6 +225,26 @@ public static void SearchingDockerRegistryCredential(this ILogger logger, string
_SearchingDockerRegistryCredential(logger, credentialStore, null);
}
+ public static void DockerRegistryAuthPropertyValueKindInvalid(this ILogger logger, string dockerRegistry, JsonValueKind valueKind)
+ {
+ _DockerRegistryAuthPropertyValueKindInvalid(logger, dockerRegistry, valueKind, null);
+ }
+
+ public static void DockerRegistryAuthPropertyValueNotFound(this ILogger logger, string dockerRegistry)
+ {
+ _DockerRegistryAuthPropertyValueNotFound(logger, dockerRegistry, null);
+ }
+
+ public static void DockerRegistryAuthPropertyValueInvalidBase64(this ILogger logger, string dockerRegistry, Exception e)
+ {
+ _DockerRegistryAuthPropertyValueInvalidBase64(logger, dockerRegistry, e);
+ }
+
+ public static void DockerRegistryAuthPropertyValueInvalidBasicAuthenticationFormat(this ILogger logger, string dockerRegistry)
+ {
+ _DockerRegistryAuthPropertyValueInvalidBasicAuthenticationFormat(logger, dockerRegistry, null);
+ }
+
public static void DockerRegistryCredentialNotFound(this ILogger logger, string dockerRegistry)
{
_DockerRegistryCredentialNotFound(logger, dockerRegistry, null);
diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs
index 11a080559..27beda932 100644
--- a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs
+++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs
@@ -1,6 +1,7 @@
namespace DotNet.Testcontainers.Tests.Unit
{
using System;
+ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@@ -8,6 +9,7 @@ namespace DotNet.Testcontainers.Tests.Unit
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Images;
+ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
@@ -62,6 +64,8 @@ public void ShouldGetDefaultDockerRegistryAuthenticationConfiguration()
public sealed class Base64ProviderTest
{
+ private readonly WarnLogger _warnLogger = new WarnLogger();
+
[Theory]
[InlineData("{\"auths\":{\"ghcr.io\":{}}}")]
[InlineData("{\"auths\":{\"://ghcr.io\":{}}}")]
@@ -79,26 +83,37 @@ public void ResolvePartialDockerRegistry(string jsonDocument)
}
[Theory]
- [InlineData("{}", false)]
- [InlineData("{\"auths\":null}", false)]
- [InlineData("{\"auths\":{}}", false)]
- [InlineData("{\"auths\":{\"ghcr.io\":{}}}", false)]
- [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{}}}", true)]
- [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":null}}}", true)]
- [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"\"}}}", true)]
- [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"dXNlcm5hbWU=\"}}}", true)]
- public void ShouldGetNull(string jsonDocument, bool isApplicable)
+ [InlineData("{}", false, null)]
+ [InlineData("{\"auths\":null}", false, null)]
+ [InlineData("{\"auths\":{}}", false, null)]
+ [InlineData("{\"auths\":{\"ghcr.io\":{}}}", false, null)]
+ [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{}}}", true, null)]
+ [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":null}}}", true, "The \"auth\" property value for https://index.docker.io/v1/ not found")]
+ [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"\"}}}", true, "The \"auth\" property value for https://index.docker.io/v1/ not found")]
+ [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":{}}}}", true, "The \"auth\" property value kind for https://index.docker.io/v1/ is invalid: Object")]
+ [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"Not_Base64_encoded\"}}}", true, "The \"auth\" property value for https://index.docker.io/v1/ is not a valid Base64 string")]
+ [InlineData("{\"auths\":{\"" + DockerRegistry + "\":{\"auth\":\"dXNlcm5hbWU=\"}}}", true, "The \"auth\" property value for https://index.docker.io/v1/ should contain one colon separating the username and the password (basic authentication)")]
+ public void ShouldGetNull(string jsonDocument, bool isApplicable, string logMessage)
{
// Given
var jsonElement = JsonDocument.Parse(jsonDocument).RootElement;
// When
- var authenticationProvider = new Base64Provider(jsonElement, NullLogger.Instance);
+ var authenticationProvider = new Base64Provider(jsonElement, _warnLogger);
var authConfig = authenticationProvider.GetAuthConfig(DockerRegistry);
// Then
Assert.Equal(isApplicable, authenticationProvider.IsApplicable(DockerRegistry));
Assert.Null(authConfig);
+
+ if (string.IsNullOrEmpty(logMessage))
+ {
+ Assert.Empty(_warnLogger.LogMessages);
+ }
+ else
+ {
+ Assert.Single(_warnLogger.LogMessages, item => logMessage.Equals(item.Item2));
+ }
}
[Fact]
@@ -223,5 +238,48 @@ static SetEnvVarPath()
.Distinct()));
}
}
+
+ private sealed class Disposable : IDisposable
+ {
+ static Disposable()
+ {
+ }
+
+ private Disposable()
+ {
+ }
+
+ public static IDisposable Empty { get; }
+ = new Disposable();
+
+ public void Dispose()
+ {
+ }
+ }
+
+ private sealed class WarnLogger : ILogger
+ {
+ private readonly List> _logMessages = new List>();
+
+ public IEnumerable> LogMessages => _logMessages;
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (IsEnabled(logLevel))
+ {
+ _logMessages.Add(Tuple.Create(logLevel, formatter.Invoke(state, exception)));
+ }
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return LogLevel.Warning.Equals(logLevel);
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return Disposable.Empty;
+ }
+ }
}
}