From 05f3380ddfa85abd7ac5814eac9051dd6221b0eb Mon Sep 17 00:00:00 2001 From: "s.naidenov" Date: Fri, 31 Jan 2025 13:52:17 +0700 Subject: [PATCH 1/9] DEV-532 --- .../Assets/E2E/access-request.config | 14 +++ .../Assets/E2E/root.config | 8 ++ .../Assets/E2E/status-server.config | 13 +++ .../E2E/Constants/RadiusAdapterConfigs.cs | 10 +++ .../E2E/Constants/RadiusAdapterConstants.cs | 9 ++ .../E2E/E2ETestBase.cs | 86 +++++++++++++++++++ .../E2E/E2ETestsUtils.cs | 28 ++++++ .../E2E/RadiusFixtures.cs | 35 ++++++++ .../E2E/Tests/AccessRequestTests.cs | 73 ++++++++++++++++ .../E2E/Tests/StatusServerTests.cs | 73 ++++++++++++++++ .../E2E/Udp/UdpData.cs | 25 ++++++ .../E2E/Udp/UdpSocket.cs | 44 ++++++++++ .../Fixtures/TestEnvironment.cs | 4 +- .../MultiFactor.Radius.Adapter.Tests.csproj | 6 ++ 14 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/access-request.config create mode 100644 src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/root.config create mode 100644 src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/status-server.config create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/access-request.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/access-request.config new file mode 100644 index 00000000..6aa04e25 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/access-request.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/root.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/root.config new file mode 100644 index 00000000..d14f0d34 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/root.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/status-server.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/status-server.config new file mode 100644 index 00000000..1a31df5b --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/status-server.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs new file mode 100644 index 00000000..db25499d --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs @@ -0,0 +1,10 @@ +namespace MultiFactor.Radius.Adapter.Tests.E2E.Constants; + +internal static class RadiusAdapterConfigs +{ + public const string StatusServerConfig = "status-server.config"; + + public const string AccessRequestConfig = "access-request.config"; + + public const string RootConfig = "root.config"; +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs new file mode 100644 index 00000000..bc6b770b --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs @@ -0,0 +1,9 @@ +namespace MultiFactor.Radius.Adapter.Tests.E2E.Constants; + +internal static class RadiusAdapterConstants +{ + public const string LocalHost = "127.0.0.1"; + public const int DefaultRadiusAdapterPort = 1812; + public const string DefaultSharedSecret = "000"; + public const string DefaultNasIdentifier = "e2e"; +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs new file mode 100644 index 00000000..791fc34d --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.Hosting; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Radius; +using MultiFactor.Radius.Adapter.Tests.E2E.Udp; +using MultiFactor.Radius.Adapter.Tests.Fixtures.Radius; + +namespace MultiFactor.Radius.Adapter.Tests.E2E; + +public abstract class E2ETestBase : IDisposable +{ + private IHost? _host = null; + private readonly RadiusHostApplicationBuilder _radiusHostApplicationBuilder; + private readonly RadiusPacketParser _packetParser; + private readonly SharedSecret _secret; + private readonly UdpSocket _udpSocket; + + protected E2ETestBase(RadiusFixtures radiusFixtures) + { + _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder(new[] { "--environment", "Test" }); + _packetParser = radiusFixtures.Parser; + _secret = radiusFixtures.SharedSecret; + _udpSocket = radiusFixtures.UdpSocket; + } + + private protected async Task StartHostAsync(Action? configure = null) + { + configure?.Invoke(_radiusHostApplicationBuilder); + _host = _radiusHostApplicationBuilder.Build(); + + await _host.StartAsync(); + } + + protected IRadiusPacket SendPacketAsync(IRadiusPacket radiusPacket) + { + if (radiusPacket is null) + { + throw new ArgumentNullException(nameof(radiusPacket)); + } + + var packetBytes = _packetParser.GetBytes(radiusPacket); + _udpSocket.Send(packetBytes); + + var data = _udpSocket.Receive(); + var parsed = _packetParser.Parse(data.GetBytes(), _secret); + + return parsed; + } + + protected IRadiusPacket CreateRadiusPacket(PacketCode packetCode, Dictionary additionalAttributes = null) + { + IRadiusPacket packet = null; + switch (packetCode) + { + case PacketCode.AccessRequest: + packet = RadiusPacketFactory.AccessRequest(); + break; + case PacketCode.StatusServer: + packet = RadiusPacketFactory.StatusServer(); + break; + case PacketCode.AccessChallenge: + packet = RadiusPacketFactory.AccessChallenge(); + break; + case PacketCode.AccessReject: + packet = RadiusPacketFactory.AccessReject(); + break; + default: + throw new NotImplementedException(); + } + + if (additionalAttributes?.Count > 0) + { + foreach (var attribute in additionalAttributes) + { + packet.AddAttribute(attribute.Key, attribute.Value); + } + } + + return packet; + } + + public void Dispose() + { + _host?.StopAsync(); + _host?.Dispose(); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs new file mode 100644 index 00000000..d17110dc --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs @@ -0,0 +1,28 @@ +using System.Net; +using Microsoft.Extensions.Logging.Abstractions; +using MultiFactor.Radius.Adapter.Core; +using MultiFactor.Radius.Adapter.Core.Radius; +using MultiFactor.Radius.Adapter.Core.Radius.Attributes; +using MultiFactor.Radius.Adapter.Tests.E2E.Udp; + +namespace MultiFactor.Radius.Adapter.Tests.E2E; + +internal static class E2ETestsUtils +{ + internal static RadiusPacketParser GetRadiusPacketParser() + { + var appVar = new ApplicationVariables + { + AppPath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) ?? throw new Exception() + }; + var dict = new RadiusDictionary(appVar); + dict.Read(); + + return new RadiusPacketParser(NullLogger.Instance, dict); + } + + internal static UdpSocket GetUdpSocket(string ip, int port) + { + return new UdpSocket(IPAddress.Parse(ip), port); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs new file mode 100644 index 00000000..cd16afde --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs @@ -0,0 +1,35 @@ +using MultiFactor.Radius.Adapter.Core.Radius; +using MultiFactor.Radius.Adapter.Tests.E2E.Constants; +using MultiFactor.Radius.Adapter.Tests.E2E.Udp; + +namespace MultiFactor.Radius.Adapter.Tests.E2E; + +public class RadiusFixtures : IDisposable +{ + public RadiusPacketParser Parser { get; } + + public UdpSocket UdpSocket { get; } + + public SharedSecret SharedSecret { get; } + + public RadiusFixtures() + { + Parser = E2ETestsUtils.GetRadiusPacketParser(); + + UdpSocket = E2ETestsUtils.GetUdpSocket( + RadiusAdapterConstants.LocalHost, + RadiusAdapterConstants.DefaultRadiusAdapterPort); + + SharedSecret = new SharedSecret(RadiusAdapterConstants.DefaultSharedSecret); + } + + public void Dispose() + { + UdpSocket.Dispose(); + } +} + +[CollectionDefinition("Radius e2e")] +public class RadiusFixturesCollection : ICollectionFixture +{ +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs new file mode 100644 index 00000000..b8e75374 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Radius; +using MultiFactor.Radius.Adapter.Extensions; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; +using MultiFactor.Radius.Adapter.Server.Pipeline.AccessRequestFilter; +using MultiFactor.Radius.Adapter.Server.Pipeline.FirstFactorAuthentication; +using MultiFactor.Radius.Adapter.Server.Pipeline.StatusServer; +using MultiFactor.Radius.Adapter.Tests.E2E.Constants; +using MultiFactor.Radius.Adapter.Tests.Fixtures; +using MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; + +namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; + +[Collection("Radius e2e")] +public class AccessRequestTests : E2ETestBase +{ + public AccessRequestTests(RadiusFixtures radiusFixtures) : base(radiusFixtures) + { + } + + [Fact] + public async Task SendAuthRequestWithoutCredentials_ShouldReject() + { + await StartHostAsync(ConfigureHost); + + var accessRequest = CreateRadiusPacket( + PacketCode.AccessRequest, + new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + } + + private void ConfigureHost(RadiusHostApplicationBuilder builder) + { + builder.AddLogging(); + + builder.Services.AddOptions(); + + builder.Services.ReplaceService(prov => + { + var opt = prov.GetRequiredService>().Value; + var rootConfig = TestRootConfigProvider.GetRootConfiguration(opt); + var factory = prov.GetRequiredService(); + + var config = factory.CreateConfig(rootConfig); + config.Validate(); + + return config; + }); + + builder.Services.ReplaceService(); + + builder.ConfigureApplication(b => + { + b.Configure(x => + { + x.RootConfigFilePath = TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.RootConfig); + x.ClientConfigFilePaths = new[] + { TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.AccessRequestConfig) }; + }); + }); + + builder.UseMiddleware(); + builder.UseMiddleware(); + builder.UseMiddleware(); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs new file mode 100644 index 00000000..f938c12b --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Radius; +using MultiFactor.Radius.Adapter.Extensions; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; +using MultiFactor.Radius.Adapter.Server.Pipeline.StatusServer; +using MultiFactor.Radius.Adapter.Tests.E2E.Constants; +using MultiFactor.Radius.Adapter.Tests.Fixtures; +using MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; + +namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; + +[Collection("Radius e2e")] +public class StatusServerTests : E2ETestBase +{ + public StatusServerTests(RadiusFixtures radiusFixtures) : base(radiusFixtures) + { + } + + [Fact] + public async Task GetServerStatus_ShouldSuccess() + { + await StartHostAsync(ConfigureHost); + + var serverStatusPacket = CreateRadiusPacket( + PacketCode.StatusServer, + new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); + + var response = SendPacketAsync(serverStatusPacket); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + + var replyMessage = response.Attributes["Reply-Message"].FirstOrDefault()?.ToString(); + Assert.NotNull(replyMessage); + Assert.StartsWith("Server up", replyMessage); + } + + private void ConfigureHost(RadiusHostApplicationBuilder builder) + { + builder.AddLogging(); + + builder.Services.AddOptions(); + + builder.Services.ReplaceService(prov => + { + var opt = prov.GetRequiredService>().Value; + var rootConfig = TestRootConfigProvider.GetRootConfiguration(opt); + var factory = prov.GetRequiredService(); + + var config = factory.CreateConfig(rootConfig); + config.Validate(); + + return config; + }); + + builder.Services.ReplaceService(); + + builder.ConfigureApplication(b => + { + b.Configure(x => + { + x.RootConfigFilePath = TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.RootConfig); + x.ClientConfigFilePaths = new[] + { TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.StatusServerConfig) }; + }); + }); + + builder.UseMiddleware(); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs new file mode 100644 index 00000000..5e8a3967 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs @@ -0,0 +1,25 @@ +using System.Text; + +namespace MultiFactor.Radius.Adapter.Tests.E2E.Udp; + +public record UdpData +{ + private readonly Memory _memory; + private byte[]? _bytes; + private string? _string; + + public UdpData(Memory bytes) + { + _memory = bytes; + } + + public byte[] GetBytes() + { + return _bytes ??= _memory.ToArray(); + } + + public string GetString() + { + return _string ??= Encoding.ASCII.GetString(GetBytes()); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs new file mode 100644 index 00000000..a3ce3f10 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs @@ -0,0 +1,44 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace MultiFactor.Radius.Adapter.Tests.E2E.Udp; + +public class UdpSocket : IDisposable +{ + private readonly IPEndPoint _endPoint; + private readonly Socket _socket; + private const int MaxUdpSize = 65_535; + + public UdpSocket(IPAddress ip, int port) + { + _endPoint = new IPEndPoint(ip, port); + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true); + } + + public void Send(string data) + { + var bytes = Encoding.ASCII.GetBytes(data); + Send(bytes); + } + + public void Send(byte[] data) + { + _socket.SendTo(data, _endPoint); + } + + public UdpData Receive() + { + var buffer = new byte[MaxUdpSize]; + var ep = (EndPoint)_endPoint; + var received = _socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref ep); + var m = new Memory(buffer, 0, received); + return new UdpData(m); + } + + public void Dispose() + { + _socket.Dispose(); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs index 2baf51d0..c977d8b2 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs @@ -3,7 +3,8 @@ internal enum TestAssetLocation { RootDirectory, - ClientsDirectory + ClientsDirectory, + E2E } internal static class TestEnvironment @@ -22,6 +23,7 @@ public static string GetAssetPath(TestAssetLocation location) return location switch { TestAssetLocation.ClientsDirectory => $"{_assetsFolder}{Path.DirectorySeparatorChar}clients", + TestAssetLocation.E2E => $"{_assetsFolder}{Path.DirectorySeparatorChar}E2E", _ => _assetsFolder, }; } diff --git a/src/MultiFactor.Radius.Adapter.Tests/MultiFactor.Radius.Adapter.Tests.csproj b/src/MultiFactor.Radius.Adapter.Tests/MultiFactor.Radius.Adapter.Tests.csproj index 4609561c..07d76459 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/MultiFactor.Radius.Adapter.Tests.csproj +++ b/src/MultiFactor.Radius.Adapter.Tests/MultiFactor.Radius.Adapter.Tests.csproj @@ -343,4 +343,10 @@ + + + Always + + + From 46a3eaec66d34a36a93a726c271cbf17f0e86de6 Mon Sep 17 00:00:00 2001 From: "s.naidenov" Date: Mon, 3 Feb 2025 09:48:56 +0700 Subject: [PATCH 2/9] DEV-532 review fixes --- .../E2E/E2ETestBase.cs | 84 ++++++++++++++----- .../E2E/Tests/AccessRequestTests.cs | 56 ++----------- .../E2E/Tests/StatusServerTests.cs | 54 ++---------- .../Fixtures/Radius/EmptyPacket.cs | 20 ++--- .../Core/Radius/IRadiusPacket.cs | 3 + .../Core/Radius/RadiusPacket.cs | 8 ++ .../RadiusHostApplicationBuilderExtensions.cs | 16 ++++ src/MultiFactor.Radius.Adapter/Program.cs | 15 +--- 8 files changed, 113 insertions(+), 143 deletions(-) diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs index 791fc34d..7152a5e5 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs @@ -1,7 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Core.Radius; +using MultiFactor.Radius.Adapter.Extensions; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; using MultiFactor.Radius.Adapter.Tests.E2E.Udp; +using MultiFactor.Radius.Adapter.Tests.Fixtures; +using MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; using MultiFactor.Radius.Adapter.Tests.Fixtures.Radius; namespace MultiFactor.Radius.Adapter.Tests.E2E; @@ -13,7 +20,7 @@ public abstract class E2ETestBase : IDisposable private readonly RadiusPacketParser _packetParser; private readonly SharedSecret _secret; private readonly UdpSocket _udpSocket; - + protected E2ETestBase(RadiusFixtures radiusFixtures) { _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder(new[] { "--environment", "Test" }); @@ -21,12 +28,39 @@ protected E2ETestBase(RadiusFixtures radiusFixtures) _secret = radiusFixtures.SharedSecret; _udpSocket = radiusFixtures.UdpSocket; } - - private protected async Task StartHostAsync(Action? configure = null) + + private protected async Task StartHostAsync( + string rootConfigName, + string[] clientConfigFileNames = null, + Action? configure = null) { + _radiusHostApplicationBuilder.AddLogging(); + + _radiusHostApplicationBuilder.Services.AddOptions(); + _radiusHostApplicationBuilder.Services.ReplaceService(prov => + { + var opt = prov.GetRequiredService>().Value; + var rootConfig = TestRootConfigProvider.GetRootConfiguration(opt); + var factory = prov.GetRequiredService(); + + var config = factory.CreateConfig(rootConfig); + config.Validate(); + + return config; + }); + + _radiusHostApplicationBuilder.Services + .ReplaceService(); + + _radiusHostApplicationBuilder.AddMiddlewares(); + + _radiusHostApplicationBuilder.ConfigureApplication(); + + ReplaceRadiusConfigs(rootConfigName, clientConfigFileNames); + configure?.Invoke(_radiusHostApplicationBuilder); _host = _radiusHostApplicationBuilder.Build(); - + await _host.StartAsync(); } @@ -39,45 +73,57 @@ protected IRadiusPacket SendPacketAsync(IRadiusPacket radiusPacket) var packetBytes = _packetParser.GetBytes(radiusPacket); _udpSocket.Send(packetBytes); - + var data = _udpSocket.Receive(); var parsed = _packetParser.Parse(data.GetBytes(), _secret); - + return parsed; } - protected IRadiusPacket CreateRadiusPacket(PacketCode packetCode, Dictionary additionalAttributes = null) + protected IRadiusPacket CreateRadiusPacket(PacketCode packetCode, SharedSecret secret = null) { IRadiusPacket packet = null; switch (packetCode) { case PacketCode.AccessRequest: - packet = RadiusPacketFactory.AccessRequest(); + packet = RadiusPacketFactory.AccessRequest(secret ?? _secret); break; case PacketCode.StatusServer: - packet = RadiusPacketFactory.StatusServer(); + packet = RadiusPacketFactory.StatusServer(secret ?? _secret); break; case PacketCode.AccessChallenge: - packet = RadiusPacketFactory.AccessChallenge(); + packet = RadiusPacketFactory.AccessChallenge(secret ?? _secret); break; case PacketCode.AccessReject: - packet = RadiusPacketFactory.AccessReject(); + packet = RadiusPacketFactory.AccessReject(secret ?? _secret); break; default: throw new NotImplementedException(); } - if (additionalAttributes?.Count > 0) - { - foreach (var attribute in additionalAttributes) - { - packet.AddAttribute(attribute.Key, attribute.Value); - } - } - return packet; } + private void ReplaceRadiusConfigs( + string rootConfigName, + string[] clientConfigFileNames = null) + { + if (string.IsNullOrEmpty(rootConfigName)) + throw new ArgumentException("Empty config path"); + + var clientConfigs = clientConfigFileNames? + .Select(configPath => TestEnvironment.GetAssetPath(TestAssetLocation.E2E, configPath)) + .ToArray() ?? Array.Empty(); + + var rootConfig = TestEnvironment.GetAssetPath(TestAssetLocation.E2E, rootConfigName); + + _radiusHostApplicationBuilder.Services.Configure(x => + { + x.RootConfigFilePath = rootConfig; + x.ClientConfigFilePaths = clientConfigs; + }); + } + public void Dispose() { _host?.StopAsync(); diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs index b8e75374..420086c6 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs @@ -1,16 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Core.Radius; -using MultiFactor.Radius.Adapter.Extensions; -using MultiFactor.Radius.Adapter.Infrastructure.Configuration; -using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; -using MultiFactor.Radius.Adapter.Server.Pipeline.AccessRequestFilter; -using MultiFactor.Radius.Adapter.Server.Pipeline.FirstFactorAuthentication; -using MultiFactor.Radius.Adapter.Server.Pipeline.StatusServer; using MultiFactor.Radius.Adapter.Tests.E2E.Constants; -using MultiFactor.Radius.Adapter.Tests.Fixtures; -using MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; @@ -24,50 +14,14 @@ public AccessRequestTests(RadiusFixtures radiusFixtures) : base(radiusFixtures) [Fact] public async Task SendAuthRequestWithoutCredentials_ShouldReject() { - await StartHostAsync(ConfigureHost); + await StartHostAsync(RadiusAdapterConfigs.RootConfig, new[] { RadiusAdapterConfigs.AccessRequestConfig }); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); - var accessRequest = CreateRadiusPacket( - PacketCode.AccessRequest, - new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); - var response = SendPacketAsync(accessRequest); - + Assert.NotNull(response); Assert.Equal(PacketCode.AccessReject, response.Header.Code); } - - private void ConfigureHost(RadiusHostApplicationBuilder builder) - { - builder.AddLogging(); - - builder.Services.AddOptions(); - - builder.Services.ReplaceService(prov => - { - var opt = prov.GetRequiredService>().Value; - var rootConfig = TestRootConfigProvider.GetRootConfiguration(opt); - var factory = prov.GetRequiredService(); - - var config = factory.CreateConfig(rootConfig); - config.Validate(); - - return config; - }); - - builder.Services.ReplaceService(); - - builder.ConfigureApplication(b => - { - b.Configure(x => - { - x.RootConfigFilePath = TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.RootConfig); - x.ClientConfigFilePaths = new[] - { TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.AccessRequestConfig) }; - }); - }); - - builder.UseMiddleware(); - builder.UseMiddleware(); - builder.UseMiddleware(); - } } \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs index f938c12b..d0cd08e4 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs @@ -1,14 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Core.Radius; -using MultiFactor.Radius.Adapter.Extensions; -using MultiFactor.Radius.Adapter.Infrastructure.Configuration; -using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; -using MultiFactor.Radius.Adapter.Server.Pipeline.StatusServer; using MultiFactor.Radius.Adapter.Tests.E2E.Constants; -using MultiFactor.Radius.Adapter.Tests.Fixtures; -using MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; @@ -22,52 +13,19 @@ public StatusServerTests(RadiusFixtures radiusFixtures) : base(radiusFixtures) [Fact] public async Task GetServerStatus_ShouldSuccess() { - await StartHostAsync(ConfigureHost); + await StartHostAsync(RadiusAdapterConfigs.RootConfig, new[] { RadiusAdapterConfigs.StatusServerConfig }); - var serverStatusPacket = CreateRadiusPacket( - PacketCode.StatusServer, - new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); + var serverStatusPacket = CreateRadiusPacket(PacketCode.StatusServer); + + serverStatusPacket.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); var response = SendPacketAsync(serverStatusPacket); - + Assert.NotNull(response); Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - + var replyMessage = response.Attributes["Reply-Message"].FirstOrDefault()?.ToString(); Assert.NotNull(replyMessage); Assert.StartsWith("Server up", replyMessage); } - - private void ConfigureHost(RadiusHostApplicationBuilder builder) - { - builder.AddLogging(); - - builder.Services.AddOptions(); - - builder.Services.ReplaceService(prov => - { - var opt = prov.GetRequiredService>().Value; - var rootConfig = TestRootConfigProvider.GetRootConfiguration(opt); - var factory = prov.GetRequiredService(); - - var config = factory.CreateConfig(rootConfig); - config.Validate(); - - return config; - }); - - builder.Services.ReplaceService(); - - builder.ConfigureApplication(b => - { - b.Configure(x => - { - x.RootConfigFilePath = TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.RootConfig); - x.ClientConfigFilePaths = new[] - { TestEnvironment.GetAssetPath(TestAssetLocation.E2E, RadiusAdapterConfigs.StatusServerConfig) }; - }); - }); - - builder.UseMiddleware(); - } } \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs index 686d72e9..27b39215 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs @@ -5,38 +5,34 @@ namespace MultiFactor.Radius.Adapter.Tests.Fixtures.Radius; internal static class RadiusPacketFactory { - public static IRadiusPacket AccessRequest() + public static IRadiusPacket AccessRequest(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.AccessRequest, 0); - var secret = Convert.ToHexString(GenerateSecret()).ToLower(); - var sharedSecret = new SharedSecret(secret); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } - public static IRadiusPacket AccessChallenge() + public static IRadiusPacket AccessChallenge(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.AccessChallenge, 0); - var secret = Convert.ToHexString(GenerateSecret()).ToLower(); - var sharedSecret = new SharedSecret(secret); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } - public static IRadiusPacket AccessReject() + public static IRadiusPacket AccessReject(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.AccessReject, 0); - var secret = Convert.ToHexString(GenerateSecret()).ToLower(); - var sharedSecret = new SharedSecret(secret); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } - public static IRadiusPacket StatusServer() + public static IRadiusPacket StatusServer(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.StatusServer, 0); - var secret = Convert.ToHexString(GenerateSecret()).ToLower(); - var sharedSecret = new SharedSecret(secret); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } diff --git a/src/MultiFactor.Radius.Adapter/Core/Radius/IRadiusPacket.cs b/src/MultiFactor.Radius.Adapter/Core/Radius/IRadiusPacket.cs index 77e89146..b20177bb 100644 --- a/src/MultiFactor.Radius.Adapter/Core/Radius/IRadiusPacket.cs +++ b/src/MultiFactor.Radius.Adapter/Core/Radius/IRadiusPacket.cs @@ -62,6 +62,9 @@ public interface IRadiusPacket void AddAttribute(string name, uint value); void AddAttribute(string name, IPAddress value); void AddAttribute(string name, byte[] value); + + void AddAttributes(IDictionary attributes); + IRadiusPacket UpdateAttribute(string name, string value); void CopyTo(IRadiusPacket packet); IRadiusPacket Clone(); diff --git a/src/MultiFactor.Radius.Adapter/Core/Radius/RadiusPacket.cs b/src/MultiFactor.Radius.Adapter/Core/Radius/RadiusPacket.cs index 43b711e9..fc6a3acd 100644 --- a/src/MultiFactor.Radius.Adapter/Core/Radius/RadiusPacket.cs +++ b/src/MultiFactor.Radius.Adapter/Core/Radius/RadiusPacket.cs @@ -276,6 +276,14 @@ public void AddAttribute(string name, byte[] value) AddAttributeObject(name, value); } + public void AddAttributes(IDictionary attributes) + { + foreach (var attr in attributes) + { + AddAttributeObject(attr.Key, attr.Value); + } + } + public IRadiusPacket UpdateAttribute(string name, string value) { if (Attributes.ContainsKey(name)) diff --git a/src/MultiFactor.Radius.Adapter/Extensions/RadiusHostApplicationBuilderExtensions.cs b/src/MultiFactor.Radius.Adapter/Extensions/RadiusHostApplicationBuilderExtensions.cs index 383785d4..155ac9ba 100644 --- a/src/MultiFactor.Radius.Adapter/Extensions/RadiusHostApplicationBuilderExtensions.cs +++ b/src/MultiFactor.Radius.Adapter/Extensions/RadiusHostApplicationBuilderExtensions.cs @@ -12,6 +12,11 @@ using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using Serilog; using System; +using MultiFactor.Radius.Adapter.Server.Pipeline.AccessRequestFilter; +using MultiFactor.Radius.Adapter.Server.Pipeline.FirstFactorAuthentication; +using MultiFactor.Radius.Adapter.Server.Pipeline.PreSecondFactorAuthentication; +using MultiFactor.Radius.Adapter.Server.Pipeline.SecondFactorAuthentication; +using MultiFactor.Radius.Adapter.Server.Pipeline.StatusServer; namespace MultiFactor.Radius.Adapter.Extensions; @@ -60,4 +65,15 @@ public static void AddLogging(this RadiusHostApplicationBuilder builder) builder.InternalHostApplicationBuilder.Logging.ClearProviders(); builder.InternalHostApplicationBuilder.Logging.AddSerilog(logger); } + + public static void AddMiddlewares(this RadiusHostApplicationBuilder builder) + { + builder.UseMiddleware(); + builder.UseMiddleware(); + builder.UseMiddleware(); + builder.UseMiddleware(); + builder.UseMiddleware(); + builder.UseMiddleware(); + builder.UseMiddleware(); + } } \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter/Program.cs b/src/MultiFactor.Radius.Adapter/Program.cs index e52843b6..79990e5e 100644 --- a/src/MultiFactor.Radius.Adapter/Program.cs +++ b/src/MultiFactor.Radius.Adapter/Program.cs @@ -1,12 +1,6 @@ using Microsoft.Extensions.Hosting; using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Extensions; -using MultiFactor.Radius.Adapter.Server.Pipeline.AccessChallenge; -using MultiFactor.Radius.Adapter.Server.Pipeline.AccessRequestFilter; -using MultiFactor.Radius.Adapter.Server.Pipeline.FirstFactorAuthentication; -using MultiFactor.Radius.Adapter.Server.Pipeline.PreSecondFactorAuthentication; -using MultiFactor.Radius.Adapter.Server.Pipeline.SecondFactorAuthentication; -using MultiFactor.Radius.Adapter.Server.Pipeline.StatusServer; using System; using System.Text; using System.Threading.Tasks; @@ -20,13 +14,8 @@ builder.AddLogging(); builder.ConfigureApplication(); - builder.UseMiddleware(); - builder.UseMiddleware(); - builder.UseMiddleware(); - builder.UseMiddleware(); - builder.UseMiddleware(); - builder.UseMiddleware(); - builder.UseMiddleware(); + builder.AddMiddlewares(); + host = builder.Build(); host.Run(); } From 3639d2d754fc5f0ca79a3ec1eaf4aba8c64c1ef7 Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Fri, 7 Feb 2025 20:12:53 +0700 Subject: [PATCH 3/9] DEV-545 sensitive data (#64) DEV-545 --- .gitignore | 3 + .../{ => BaseConfigs}/access-request.config | 0 .../Assets/E2E/{ => BaseConfigs}/root.config | 0 .../{ => BaseConfigs}/status-server.config | 0 .../E2E/Constants/RadiusAdapterConstants.cs | 9 ++ .../E2E/E2ETestBase.cs | 21 ++-- .../E2E/E2ETestSensitiveSettings.cs | 29 +++++ .../E2E/E2ETestsUtils.cs | 37 +++++++ .../E2E/Tests/AccessRequestTests.cs | 104 +++++++++++++++++- .../TestClientConfigsProvider.cs | 24 +++- .../TestConfigProviderOptions.cs | 1 + .../Fixtures/TestEnvironment.cs | 6 +- .../Fixtures/TestEnvironmentVariables.cs | 27 +++++ .../ConfigurationBuilderExtensions.cs | 9 +- .../DefaultClientConfigurationsProvider.cs | 2 +- .../RadiusAdapterConfigurationFactory.cs | 13 ++- 16 files changed, 259 insertions(+), 26 deletions(-) rename src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/{ => BaseConfigs}/access-request.config (100%) rename src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/{ => BaseConfigs}/root.config (100%) rename src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/{ => BaseConfigs}/status-server.config (100%) create mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs diff --git a/.gitignore b/.gitignore index 941a36c2..b375fbaf 100644 --- a/.gitignore +++ b/.gitignore @@ -339,3 +339,6 @@ ASALocalRun/ # BeatPulse healthcheck temp database healthchecksdb src/Properties/launchSettings.json + +# E2E sensitive data folder +src\MultiFactor.Radius.Adapter.Tests\Assets\E2E\SensitiveData \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/access-request.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/access-request.config similarity index 100% rename from src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/access-request.config rename to src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/access-request.config diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/root.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root.config similarity index 100% rename from src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/root.config rename to src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root.config diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/status-server.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/status-server.config similarity index 100% rename from src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/status-server.config rename to src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/status-server.config diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs index bc6b770b..a7806dc3 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs @@ -6,4 +6,13 @@ internal static class RadiusAdapterConstants public const int DefaultRadiusAdapterPort = 1812; public const string DefaultSharedSecret = "000"; public const string DefaultNasIdentifier = "e2e"; + + public const string BindUserName = "BindUser"; + public const string BindUserPassword = "Qwerty123!"; + + public const string AdminUserName = "e2eAdmin"; + public const string AdminUserPassword = "Qwerty123!"; + + public const string ChangePasswordUserName = "PasswordUser"; + public const string ChangePasswordUserPassword = "Qwerty123!"; } \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs index 7152a5e5..e84861f6 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs @@ -23,7 +23,10 @@ public abstract class E2ETestBase : IDisposable protected E2ETestBase(RadiusFixtures radiusFixtures) { - _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder(new[] { "--environment", "Test" }); + _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder(new[] + { + "--environment", "Test" + }); _packetParser = radiusFixtures.Parser; _secret = radiusFixtures.SharedSecret; _udpSocket = radiusFixtures.UdpSocket; @@ -32,6 +35,7 @@ protected E2ETestBase(RadiusFixtures radiusFixtures) private protected async Task StartHostAsync( string rootConfigName, string[] clientConfigFileNames = null, + string envPrefix = null, Action? configure = null) { _radiusHostApplicationBuilder.AddLogging(); @@ -56,9 +60,10 @@ private protected async Task StartHostAsync( _radiusHostApplicationBuilder.ConfigureApplication(); - ReplaceRadiusConfigs(rootConfigName, clientConfigFileNames); - + ReplaceRadiusConfigs(rootConfigName, clientConfigFileNames, envPrefix: envPrefix); + configure?.Invoke(_radiusHostApplicationBuilder); + _host = _radiusHostApplicationBuilder.Build(); await _host.StartAsync(); @@ -75,7 +80,7 @@ protected IRadiusPacket SendPacketAsync(IRadiusPacket radiusPacket) _udpSocket.Send(packetBytes); var data = _udpSocket.Receive(); - var parsed = _packetParser.Parse(data.GetBytes(), _secret); + var parsed = _packetParser.Parse(data.GetBytes(), _secret, radiusPacket.Authenticator.Value); return parsed; } @@ -106,21 +111,23 @@ protected IRadiusPacket CreateRadiusPacket(PacketCode packetCode, SharedSecret s private void ReplaceRadiusConfigs( string rootConfigName, - string[] clientConfigFileNames = null) + string[] clientConfigFileNames = null, + string envPrefix = null) { if (string.IsNullOrEmpty(rootConfigName)) throw new ArgumentException("Empty config path"); var clientConfigs = clientConfigFileNames? - .Select(configPath => TestEnvironment.GetAssetPath(TestAssetLocation.E2E, configPath)) + .Select(fileName => TestEnvironment.GetAssetPath(TestAssetLocation.E2EBaseConfigs, fileName)) .ToArray() ?? Array.Empty(); - var rootConfig = TestEnvironment.GetAssetPath(TestAssetLocation.E2E, rootConfigName); + var rootConfig = TestEnvironment.GetAssetPath(TestAssetLocation.E2EBaseConfigs, rootConfigName); _radiusHostApplicationBuilder.Services.Configure(x => { x.RootConfigFilePath = rootConfig; x.ClientConfigFilePaths = clientConfigs; + x.EnvironmentVariablePrefix = envPrefix; }); } diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs new file mode 100644 index 00000000..3419b542 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs @@ -0,0 +1,29 @@ +namespace MultiFactor.Radius.Adapter.Tests.E2E; + +public class E2ETestSensitiveSettings +{ + public CatalogUser User { get; set; } + public CatalogUser TechUser { get; set; } + public CatalogSettings CatalogSettings { get; set; } + public MultifactorApiSettings MultifactorApiSettings { get; set; } +} + +public class CatalogUser +{ + public string UserName { get; set; } + + public string Password { get; set; } +} + +public class MultifactorApiSettings +{ + public string NasIdentifier { get; set; } + public string SharedSecret { get; set; } +} + +public class CatalogSettings +{ + public string Hosts { get; set; } + + public string Groups { get; set; } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs index d17110dc..8519803b 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs @@ -1,9 +1,11 @@ using System.Net; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using MultiFactor.Radius.Adapter.Core; using MultiFactor.Radius.Adapter.Core.Radius; using MultiFactor.Radius.Adapter.Core.Radius.Attributes; using MultiFactor.Radius.Adapter.Tests.E2E.Udp; +using MultiFactor.Radius.Adapter.Tests.Fixtures; namespace MultiFactor.Radius.Adapter.Tests.E2E; @@ -25,4 +27,39 @@ internal static UdpSocket GetUdpSocket(string ip, int port) { return new UdpSocket(IPAddress.Parse(ip), port); } + + internal static T GetSensitiveData(string fileName, string sectionName) + { + var sensitiveDataPath = TestEnvironment.GetAssetPath(TestAssetLocation.E2ESensitiveData, fileName); + + IConfigurationRoot config = new ConfigurationBuilder() + .AddJsonFile(sensitiveDataPath, optional: false, reloadOnChange: true) + .Build(); + + var sensitiveData = config.GetRequiredSection(sectionName).Get(); + return sensitiveData; + } + + internal static Dictionary GetSensitiveData(string fileName) + { + var envs = new Dictionary(); + var sensitiveDataPath = TestEnvironment.GetAssetPath(TestAssetLocation.E2ESensitiveData, fileName); + + var lines = File.ReadLines(sensitiveDataPath); + foreach (var line in lines) + { + var parts = line.Split('='); + envs.Add(parts[0].Trim(), parts[1].Trim()); + } + + return envs; + } + + internal static string GetEnvPrefix(string envKey) + { + if (string.IsNullOrWhiteSpace(envKey)) + throw new ArgumentNullException(nameof(envKey)); + var parts = envKey.Split('_'); + return parts[0] + "_"; + } } \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs index 420086c6..e731529b 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs @@ -1,6 +1,6 @@ -using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Core.Radius; using MultiFactor.Radius.Adapter.Tests.E2E.Constants; +using MultiFactor.Radius.Adapter.Tests.Fixtures; namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; @@ -17,11 +17,111 @@ public async Task SendAuthRequestWithoutCredentials_ShouldReject() await StartHostAsync(RadiusAdapterConfigs.RootConfig, new[] { RadiusAdapterConfigs.AccessRequestConfig }); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); + accessRequest.AddAttributes(new Dictionary() + { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); var response = SendPacketAsync(accessRequest); Assert.NotNull(response); Assert.Equal(PacketCode.AccessReject, response.Header.Code); } + + [Theory] + [InlineData("ad.env")] + public async Task SendAuthRequestWithBindUser_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + RadiusAdapterConfigs.RootConfig, + new[] { RadiusAdapterConfigs.AccessRequestConfig }, + envPrefix: prefix); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad.env")] + public async Task SendAuthRequestWithAdminUser_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + RadiusAdapterConfigs.RootConfig, + new[] { RadiusAdapterConfigs.AccessRequestConfig }, + envPrefix: prefix); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.AdminUserName }, + { "User-Password", RadiusAdapterConstants.AdminUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad.env")] + public async Task SendAuthRequestWithPasswordUser_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + RadiusAdapterConfigs.RootConfig, + new[] { RadiusAdapterConfigs.AccessRequestConfig }, + envPrefix: prefix); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.ChangePasswordUserName }, + { "User-Password", RadiusAdapterConstants.ChangePasswordUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } } \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs index 241988f9..115b2f43 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs @@ -8,7 +8,7 @@ namespace MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; internal class TestClientConfigsProvider : IClientConfigurationsProvider { - private Dictionary _dict = new(); + private Dictionary _dict = new(); private readonly TestConfigProviderOptions _options; public TestClientConfigsProvider(IOptions options) @@ -23,11 +23,23 @@ public RadiusAdapterConfiguration[] GetClientConfigurations() { return Array.Empty(); } - - _dict = clientConfigFiles - .Select(x => new RadiusConfigurationFile(x)) - .ToDictionary(k => k, v => RadiusAdapterConfigurationFactory.Create(v, v.Name)); - + var fileSources = clientConfigFiles.Select(x => new RadiusConfigurationFile(x)).ToArray(); + foreach (var file in fileSources) + { + var config = RadiusAdapterConfigurationFactory.Create(file, file.Name, _options.EnvironmentVariablePrefix); + _dict.Add(file, config); + } + + var envVarSources = DefaultClientConfigurationsProvider.GetEnvVarClients() + .Select(x => new RadiusConfigurationEnvironmentVariable(x)) + .ExceptBy(fileSources.Select(x => RadiusConfigurationSource.TransformName(x.Name)), x => x.Name); + + foreach (var envVarClient in envVarSources) + { + var config = RadiusAdapterConfigurationFactory.Create(envVarClient, _options.EnvironmentVariablePrefix); + _dict.Add(envVarClient, config); + } + return _dict.Select(x => x.Value).ToArray(); } diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs index be14b934..6b78df44 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs @@ -5,4 +5,5 @@ internal class TestConfigProviderOptions public string? RootConfigFilePath { get; set; } public string? ClientConfigsFolderPath { get; set; } public string[] ClientConfigFilePaths { get; set; } = Array.Empty(); + public string EnvironmentVariablePrefix { get; set; } } diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs index c977d8b2..90ee4148 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironment.cs @@ -4,7 +4,8 @@ internal enum TestAssetLocation { RootDirectory, ClientsDirectory, - E2E + E2EBaseConfigs, + E2ESensitiveData } internal static class TestEnvironment @@ -23,7 +24,8 @@ public static string GetAssetPath(TestAssetLocation location) return location switch { TestAssetLocation.ClientsDirectory => $"{_assetsFolder}{Path.DirectorySeparatorChar}clients", - TestAssetLocation.E2E => $"{_assetsFolder}{Path.DirectorySeparatorChar}E2E", + TestAssetLocation.E2EBaseConfigs => $"{_assetsFolder}{Path.DirectorySeparatorChar}E2E{Path.DirectorySeparatorChar}BaseConfigs", + TestAssetLocation.E2ESensitiveData => $"{_assetsFolder}{Path.DirectorySeparatorChar}E2E{Path.DirectorySeparatorChar}SensitiveData", _ => _assetsFolder, }; } diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironmentVariables.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironmentVariables.cs index a431d70a..7630c310 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironmentVariables.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestEnvironmentVariables.cs @@ -28,6 +28,23 @@ public static void With(Action action) Environment.SetEnvironmentVariable(name, null); } } + + public static async Task With(Func action) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + var names = new HashSet(); + + await action(new TestEnvironmentVariables(names)); + + foreach (var name in names) + { + Environment.SetEnvironmentVariable(name, null); + } + } public TestEnvironmentVariables SetEnvironmentVariable(string name, string value) { @@ -41,4 +58,14 @@ public TestEnvironmentVariables SetEnvironmentVariable(string name, string value return this; } + + public TestEnvironmentVariables SetEnvironmentVariables(Dictionary dictionary) + { + foreach (var env in dictionary) + { + SetEnvironmentVariable(env.Key, env.Value); + } + + return this; + } } diff --git a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ConfigurationBuilderExtensions.cs b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ConfigurationBuilderExtensions.cs index 1a26e591..103c96ed 100644 --- a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ConfigurationBuilderExtensions.cs +++ b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ConfigurationBuilderExtensions.cs @@ -10,7 +10,7 @@ namespace MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationL internal static class ConfigurationBuilderExtensions { - public const string BasePrefix = "RAD_"; + public static string BasePrefix; public static IConfigurationBuilder AddRadiusConfigurationFile(this IConfigurationBuilder configurationBuilder, RadiusConfigurationFile file) { @@ -24,12 +24,17 @@ public static IConfigurationBuilder AddRadiusConfigurationFile(this IConfigurati } public static IConfigurationBuilder AddRadiusEnvironmentVariables(this IConfigurationBuilder configurationBuilder, - string configName = null) + string configName = null, + string customPrefix = null) { var preparedConfigName = RadiusConfigurationSource.TransformName(configName); + + BasePrefix = customPrefix ?? "RAD_"; + var prefix = preparedConfigName == string.Empty ? BasePrefix : $"{BasePrefix}{preparedConfigName}_"; + configurationBuilder.AddEnvironmentVariables(prefix); return configurationBuilder; } diff --git a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/DefaultClientConfigurationsProvider.cs b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/DefaultClientConfigurationsProvider.cs index 3845e4f1..0bc07e3d 100644 --- a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/DefaultClientConfigurationsProvider.cs +++ b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/DefaultClientConfigurationsProvider.cs @@ -69,7 +69,7 @@ private Dictionary Load() return dict; } - private static IEnumerable GetEnvVarClients() + internal static IEnumerable GetEnvVarClients() { var patterns = RadiusAdapterConfiguration.KnownSectionNames .Select(x => $"^(?i){ConfigurationBuilderExtensions.BasePrefix}(?[a-zA-Z_]+[a-zA-Z0-9_]*)_{x}") diff --git a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/RadiusAdapterConfigurationFactory.cs b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/RadiusAdapterConfigurationFactory.cs index 2e2193f5..fe111192 100644 --- a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/RadiusAdapterConfigurationFactory.cs +++ b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/RadiusAdapterConfigurationFactory.cs @@ -20,11 +20,12 @@ internal static class RadiusAdapterConfigurationFactory /// /// Configuration file path. /// Configuration name. + /// Custom env variable prefix. /// Radius Adapter Configuration /// /// /// - public static RadiusAdapterConfiguration Create(RadiusConfigurationFile file, string name = null) + public static RadiusAdapterConfiguration Create(RadiusConfigurationFile file, string name = null, string envPrefix = null) { if (file is null) { @@ -38,7 +39,7 @@ public static RadiusAdapterConfiguration Create(RadiusConfigurationFile file, st var config = new ConfigurationBuilder() .AddRadiusConfigurationFile(file) - .AddRadiusEnvironmentVariables(name) + .AddRadiusEnvironmentVariables(name, customPrefix: envPrefix) .Build(); var bounded = config.BindRadiusAdapterConfig(); @@ -57,7 +58,7 @@ public static RadiusAdapterConfiguration Create(RadiusConfigurationFile file, st /// Radius Adapter Configuration /// /// - public static RadiusAdapterConfiguration Create(RadiusConfigurationEnvironmentVariable environmentVariable) + public static RadiusAdapterConfiguration Create(RadiusConfigurationEnvironmentVariable environmentVariable, string envPrefix = null) { if (environmentVariable is null) { @@ -65,7 +66,7 @@ public static RadiusAdapterConfiguration Create(RadiusConfigurationEnvironmentVa } var config = new ConfigurationBuilder() - .AddRadiusEnvironmentVariables(environmentVariable.Name) + .AddRadiusEnvironmentVariables(environmentVariable.Name, customPrefix: envPrefix) .Build(); var bounded = config.BindRadiusAdapterConfig(); @@ -82,10 +83,10 @@ public static RadiusAdapterConfiguration Create(RadiusConfigurationEnvironmentVa /// /// Radius Adapter Configuration /// - public static RadiusAdapterConfiguration Create() + public static RadiusAdapterConfiguration Create(string envPrefix = null) { var config = new ConfigurationBuilder() - .AddRadiusEnvironmentVariables() + .AddRadiusEnvironmentVariables(customPrefix: envPrefix) .Build(); var bounded = config.BindRadiusAdapterConfig(); From 26d7b54f154f865d345781158fb3128d52dc7122 Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:03:50 +0700 Subject: [PATCH 4/9] DEV-546 (#65) DEV-546 BST000 - BST013 --- .gitignore | 2 +- .../AppConfigConfigurationSourceTests.cs | 14 +- .../RadiusAdapterConfigurationFactoryTests.cs | 13 +- .../RadiusConfigurationFileTests.cs | 19 -- ...t-bypass-false-when-api-unreachable.config | 13 ++ ...ot-bypass-true-when-api-unreachable.config | 13 ++ ...root-no-bypass-when-api-unreachable.config | 12 + .../E2E/E2ETestSensitiveSettings.cs | 29 --- .../E2E/RadiusFixtures.cs | 35 --- .../TestClientConfigsProvider.cs | 2 +- .../TestConfigProviderOptions.cs | 4 +- .../Fixtures/Radius/EmptyPacket.cs | 8 +- .../Fixtures/TestHost.cs | 2 +- .../RadiusConfigurationFile.cs | 17 -- .../MultiFactor.Radius.Adapter.csproj | 1 + .../Assets/BaseConfigs/access-request.config | 14 ++ .../root-active-directory-group.config | 21 ++ ...t-bypass-false-when-api-unreachable.config | 13 ++ ...ot-bypass-true-when-api-unreachable.config | 13 ++ ...ultiple-active-directory-2fa-groups.config | 21 ++ ...ot-multiple-active-directory-groups.config | 21 ++ ...existed-active-directory-2fa-groups.config | 21 ++ ...not-existed-active-directory-groups.config | 21 ++ ...root-no-bypass-when-api-unreachable.config | 12 + ...-not-existed-active-directory-group.config | 19 ++ ...t-single-active-directory-2fa-group.config | 21 ++ .../Assets/BaseConfigs/root.config | 8 + .../Assets/BaseConfigs/status-server.config | 13 ++ .../Constants/RadiusAdapterConfigs.cs | 2 +- .../Constants/RadiusAdapterConstants.cs | 2 +- .../E2ETestBase.cs | 51 ++-- .../E2ETestsUtils.cs | 26 +-- .../TestClientConfigsProvider.cs | 76 ++++++ .../TestConfigProviderOptions.cs | 9 + .../ConfigLoading/TestRootConfigProvider.cs | 33 +++ .../Fixtures/EmptyStringsListInput.cs | 19 ++ .../LdapResponse/AttributesBuilder.cs | 27 +++ .../Fixtures/LdapResponse/LdapEntryFactory.cs | 26 +++ .../Fixtures/Radius/RadiusPacketFactory.cs | 48 ++++ .../Fixtures/ServiceCollectionExtensions.cs | 78 +++++++ .../Fixtures/TestEnvironment.cs | 39 ++++ .../Fixtures/TestEnvironmentVariables.cs | 71 ++++++ ...factor.Radius.Adapter.EndToEndTests.csproj | 37 +++ .../RadiusFixtures.cs | 26 +++ .../Tests/BypassWhenApiUnreachableTests.cs | 217 ++++++++++++++++++ .../Tests/DefaultUsersBindTests.cs} | 28 +-- .../MultipleActiveDirectory2FaGroupsTests.cs | 104 +++++++++ .../MultipleActiveDirectoryGroupsTests.cs | 104 +++++++++ .../SingleActiveDirectory2FaGroupTests.cs | 59 +++++ .../Tests/SingleActiveDirectoryGroupTests.cs | 104 +++++++++ .../Tests/StatusServerTests.cs | 14 +- .../Udp/UdpData.cs | 2 +- .../Udp/UdpSocket.cs | 2 +- src/multifactor-radius-adapter.sln | 6 + 54 files changed, 1414 insertions(+), 198 deletions(-) create mode 100644 src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-false-when-api-unreachable.config create mode 100644 src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-true-when-api-unreachable.config create mode 100644 src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-no-bypass-when-api-unreachable.config delete mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs delete mode 100644 src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/Constants/RadiusAdapterConfigs.cs (80%) rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/Constants/RadiusAdapterConstants.cs (90%) rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/E2ETestBase.cs (74%) rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/E2ETestsUtils.cs (68%) create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestRootConfigProvider.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/EmptyStringsListInput.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/AttributesBuilder.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/LdapEntryFactory.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ServiceCollectionExtensions.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironment.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironmentVariables.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/RadiusFixtures.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs rename src/{MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs => Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs} (80%) create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/Tests/StatusServerTests.cs (53%) rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/Udp/UdpData.cs (88%) rename src/{MultiFactor.Radius.Adapter.Tests/E2E => Multifactor.Radius.Adapter.EndToEndTests}/Udp/UdpSocket.cs (95%) diff --git a/.gitignore b/.gitignore index b375fbaf..556d4c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -341,4 +341,4 @@ healthchecksdb src/Properties/launchSettings.json # E2E sensitive data folder -src\MultiFactor.Radius.Adapter.Tests\Assets\E2E\SensitiveData \ No newline at end of file +src\MultiFactor.Radius.Adapter.EndToEndTests\Assets\SensitiveData \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/AppConfigConfigurationSourceTests.cs b/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/AppConfigConfigurationSourceTests.cs index 69cd843c..45ce2455 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/AppConfigConfigurationSourceTests.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/AppConfigConfigurationSourceTests.cs @@ -13,7 +13,7 @@ public class AppConfigConfigurationSourceTests public void Load_ShouldLoadAndTransformNames() { var path = TestEnvironment.GetAssetPath("root-all-appsettings-items.config"); - var source = new TestableAppConfigConfigurationSource(path); + var source = new TestableAppConfigConfigurationSource(new RadiusConfigurationFile(path)); source.Load(); @@ -67,7 +67,7 @@ public void Get_ShouldBindAndAllNestedElementsNotBeNull() var path = TestEnvironment.GetAssetPath("root-minimal-multi.config"); var config = new ConfigurationBuilder() - .Add(new XmlAppConfigurationSource(path)) + .Add(new XmlAppConfigurationSource(new RadiusConfigurationFile(path))) .Build(); var bound = config.BindRadiusAdapterConfig(); @@ -88,7 +88,7 @@ public void Get_ShouldBindRadiusReplySection() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "radius-reply-join.config"); var config = new ConfigurationBuilder() - .Add(new XmlAppConfigurationSource(path)) + .Add(new XmlAppConfigurationSource(new RadiusConfigurationFile(path))) .Build(); var bound = config.BindRadiusAdapterConfig(); @@ -119,7 +119,7 @@ public void Get_Single_ShouldBindRadiusReplySection() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "radius-reply-single.config"); var config = new ConfigurationBuilder() - .Add(new XmlAppConfigurationSource(path)) + .Add(new XmlAppConfigurationSource(new RadiusConfigurationFile(path))) .Build(); var bound = config.BindRadiusAdapterConfig(); @@ -138,7 +138,7 @@ public void Get_ShouldBindUserNameTransformRulesSection() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "user-name-transform-rules.config"); var config = new ConfigurationBuilder() - .Add(new XmlAppConfigurationSource(path)) + .Add(new XmlAppConfigurationSource(new RadiusConfigurationFile(path))) .Build(); var bound = config.BindRadiusAdapterConfig(); @@ -167,7 +167,7 @@ public void Get_SingleRule_ShouldBindUserNameTransformRulesSection() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "user-name-transform-single-rule.config"); var config = new ConfigurationBuilder() - .Add(new XmlAppConfigurationSource(path)) + .Add(new XmlAppConfigurationSource(new RadiusConfigurationFile(path))) .Build(); var bound = config.BindRadiusAdapterConfig(); @@ -185,7 +185,7 @@ public void Get_BypassSecondFactorWhenApiUnreachableShouldBeTrueByDefault() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "user-name-transform-rules.config"); var config = new ConfigurationBuilder() - .Add(new XmlAppConfigurationSource(path)) + .Add(new XmlAppConfigurationSource(new RadiusConfigurationFile(path))) .Build(); var bound = config.BindRadiusAdapterConfig(); diff --git a/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusAdapterConfigurationFactoryTests.cs b/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusAdapterConfigurationFactoryTests.cs index 066a073b..e48df81e 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusAdapterConfigurationFactoryTests.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusAdapterConfigurationFactoryTests.cs @@ -1,4 +1,5 @@ using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.XmlAppConfiguration; using MultiFactor.Radius.Adapter.Tests.Fixtures; namespace MultiFactor.Radius.Adapter.Tests.AdapterConfig; @@ -9,7 +10,7 @@ public class RadiusAdapterConfigurationFactoryTests public void CreateMinimalRoot_WithNoEnvVar_ShouldCreate() { var path = TestEnvironment.GetAssetPath("root-minimal-single.config"); - var config = RadiusAdapterConfigurationFactory.Create(path); + var config = RadiusAdapterConfigurationFactory.Create(new RadiusConfigurationFile(path)); Assert.Equal("0.0.0.0:1812", config.AppSettings.AdapterServerEndpoint); Assert.Equal("000", config.AppSettings.RadiusSharedSecret); @@ -34,7 +35,7 @@ public void CreateMinimalRoot_OverrideByEnvVar_ShouldCreate() env.SetEnvironmentVariable("rad_appsettings__LoggingLevel", "Info"); var path = TestEnvironment.GetAssetPath("root-minimal-single.config"); - var config = RadiusAdapterConfigurationFactory.Create(path); + var config = RadiusAdapterConfigurationFactory.Create(new RadiusConfigurationFile(path)); Assert.Equal("0.0.0.0:1818", config.AppSettings.AdapterServerEndpoint); Assert.Equal("888", config.AppSettings.RadiusSharedSecret); @@ -51,7 +52,7 @@ public void CreateClient_WithNoEnvVar_ShouldCreate() { var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "client-minimal-for-overriding.config"); - var config = RadiusAdapterConfigurationFactory.Create(path, "client-minimal-for-overriding"); + var config = RadiusAdapterConfigurationFactory.Create(new RadiusConfigurationFile(path), "client-minimal-for-overriding"); Assert.Equal("windows", config.AppSettings.RadiusClientNasIdentifier); Assert.Equal("000", config.AppSettings.RadiusSharedSecret); @@ -78,7 +79,7 @@ public void CreateClient_OverrideByEnvVar_ShouldCreate() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "client-minimal-for-overriding.config"); - var config = RadiusAdapterConfigurationFactory.Create(path, "client-minimal-for-overriding"); + var config = RadiusAdapterConfigurationFactory.Create(new RadiusConfigurationFile(path), "client-minimal-for-overriding"); Assert.Equal("Linux", config.AppSettings.RadiusClientNasIdentifier); Assert.Equal("888", config.AppSettings.RadiusSharedSecret); @@ -98,7 +99,7 @@ public void CreateClientWithSpacedName_OverrideByEnvVar_ShouldCreate() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "client-minimal-for-overriding.config"); - var config = RadiusAdapterConfigurationFactory.Create(path, "client minimal spaced"); + var config = RadiusAdapterConfigurationFactory.Create(new RadiusConfigurationFile(path), "client minimal spaced"); Assert.Equal("Linux", config.AppSettings.RadiusClientNasIdentifier); }); @@ -119,7 +120,7 @@ public void CreateClient_ComplexPathOverrideByEnvVar_ShouldCreate() var path = TestEnvironment.GetAssetPath(TestAssetLocation.ClientsDirectory, "client-minimal-for-overriding.config"); - var config = RadiusAdapterConfigurationFactory.Create(path, "client-minimal-for-overriding"); + var config = RadiusAdapterConfigurationFactory.Create(new RadiusConfigurationFile(path), "client-minimal-for-overriding"); var attribute = Assert.Single(config.RadiusReply.Attributes.Elements); Assert.NotNull(attribute); diff --git a/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusConfigurationFileTests.cs b/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusConfigurationFileTests.cs index 9ffad1a2..6318d5c4 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusConfigurationFileTests.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/AdapterConfig/RadiusConfigurationFileTests.cs @@ -26,25 +26,6 @@ public void Create_CorrectPath_ShouldCreateAndStoreValue(string path) Assert.Equal(path, file.Path); } - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("file")] - [InlineData("file.conf")] - public void Cast_ToRadConfFileFromIncorrectPathString_ShouldThrow(string path) - { - Assert.Throws(() => (RadiusConfigurationFile)path); - } - - [Theory] - [InlineData("file.config")] - [InlineData("dir/file.config")] - public void Cast_ToRadConfFileFromCorrectPathString_ShouldSuccess(string path) - { - var file = (RadiusConfigurationFile)path; - Assert.Equal(path, file.Path); - } - [Fact] public void Cast_ToStringFromNullRadConfFile_ShouldThrow() { diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-false-when-api-unreachable.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-false-when-api-unreachable.config new file mode 100644 index 00000000..74bfe02e --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-false-when-api-unreachable.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-true-when-api-unreachable.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-true-when-api-unreachable.config new file mode 100644 index 00000000..512f7ee7 --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-bypass-true-when-api-unreachable.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-no-bypass-when-api-unreachable.config b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-no-bypass-when-api-unreachable.config new file mode 100644 index 00000000..ab909cdd --- /dev/null +++ b/src/MultiFactor.Radius.Adapter.Tests/Assets/E2E/BaseConfigs/root-no-bypass-when-api-unreachable.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs deleted file mode 100644 index 3419b542..00000000 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestSensitiveSettings.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace MultiFactor.Radius.Adapter.Tests.E2E; - -public class E2ETestSensitiveSettings -{ - public CatalogUser User { get; set; } - public CatalogUser TechUser { get; set; } - public CatalogSettings CatalogSettings { get; set; } - public MultifactorApiSettings MultifactorApiSettings { get; set; } -} - -public class CatalogUser -{ - public string UserName { get; set; } - - public string Password { get; set; } -} - -public class MultifactorApiSettings -{ - public string NasIdentifier { get; set; } - public string SharedSecret { get; set; } -} - -public class CatalogSettings -{ - public string Hosts { get; set; } - - public string Groups { get; set; } -} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs b/src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs deleted file mode 100644 index cd16afde..00000000 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/RadiusFixtures.cs +++ /dev/null @@ -1,35 +0,0 @@ -using MultiFactor.Radius.Adapter.Core.Radius; -using MultiFactor.Radius.Adapter.Tests.E2E.Constants; -using MultiFactor.Radius.Adapter.Tests.E2E.Udp; - -namespace MultiFactor.Radius.Adapter.Tests.E2E; - -public class RadiusFixtures : IDisposable -{ - public RadiusPacketParser Parser { get; } - - public UdpSocket UdpSocket { get; } - - public SharedSecret SharedSecret { get; } - - public RadiusFixtures() - { - Parser = E2ETestsUtils.GetRadiusPacketParser(); - - UdpSocket = E2ETestsUtils.GetUdpSocket( - RadiusAdapterConstants.LocalHost, - RadiusAdapterConstants.DefaultRadiusAdapterPort); - - SharedSecret = new SharedSecret(RadiusAdapterConstants.DefaultSharedSecret); - } - - public void Dispose() - { - UdpSocket.Dispose(); - } -} - -[CollectionDefinition("Radius e2e")] -public class RadiusFixturesCollection : ICollectionFixture -{ -} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs index 115b2f43..3d3a1a7d 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs @@ -50,7 +50,7 @@ public RadiusConfigurationSource GetSource(RadiusAdapterConfiguration configurat private IEnumerable GetFiles() { - if (_options.ClientConfigFilePaths != null && _options.ClientConfigFilePaths.Length != 0) + if (_options.ClientConfigFilePaths?.Length > 0) { foreach (var f in _options.ClientConfigFilePaths) { diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs index 6b78df44..f414d77c 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs @@ -4,6 +4,6 @@ internal class TestConfigProviderOptions { public string? RootConfigFilePath { get; set; } public string? ClientConfigsFolderPath { get; set; } - public string[] ClientConfigFilePaths { get; set; } = Array.Empty(); - public string EnvironmentVariablePrefix { get; set; } + public string[] ClientConfigFilePaths { get; set; } = []; + public string? EnvironmentVariablePrefix { get; set; } } diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs index 27b39215..fb6dec7f 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/Radius/EmptyPacket.cs @@ -5,7 +5,7 @@ namespace MultiFactor.Radius.Adapter.Tests.Fixtures.Radius; internal static class RadiusPacketFactory { - public static IRadiusPacket AccessRequest(SharedSecret packetSecret = null) + public static IRadiusPacket? AccessRequest(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.AccessRequest, 0); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); @@ -13,7 +13,7 @@ public static IRadiusPacket AccessRequest(SharedSecret packetSecret = null) return packet; } - public static IRadiusPacket AccessChallenge(SharedSecret packetSecret = null) + public static IRadiusPacket? AccessChallenge(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.AccessChallenge, 0); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); @@ -21,7 +21,7 @@ public static IRadiusPacket AccessChallenge(SharedSecret packetSecret = null) return packet; } - public static IRadiusPacket AccessReject(SharedSecret packetSecret = null) + public static IRadiusPacket? AccessReject(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.AccessReject, 0); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); @@ -29,7 +29,7 @@ public static IRadiusPacket AccessReject(SharedSecret packetSecret = null) return packet; } - public static IRadiusPacket StatusServer(SharedSecret packetSecret = null) + public static IRadiusPacket? StatusServer(SharedSecret packetSecret = null) { var header = RadiusPacketHeader.Create(PacketCode.StatusServer, 0); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); diff --git a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestHost.cs b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestHost.cs index 219a440e..e35f63c7 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestHost.cs +++ b/src/MultiFactor.Radius.Adapter.Tests/Fixtures/TestHost.cs @@ -29,7 +29,7 @@ public TestHost(IHost host) /// If defined, the client config will be specified. Othervise the client config will be getted from the first element of . /// Setup context action. /// - public RadiusContext CreateContext(IRadiusPacket requestPacket, + public RadiusContext CreateContext(IRadiusPacket? requestPacket, IClientConfiguration? clientConfig = null, Action? setupContext = null) { diff --git a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/XmlAppConfiguration/RadiusConfigurationFile.cs b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/XmlAppConfiguration/RadiusConfigurationFile.cs index 5893b694..3fa83475 100644 --- a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/XmlAppConfiguration/RadiusConfigurationFile.cs +++ b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/XmlAppConfiguration/RadiusConfigurationFile.cs @@ -54,22 +54,5 @@ public static implicit operator string(RadiusConfigurationFile path) return path?.Path ?? throw new InvalidCastException("Unable to cast NULL ConfigPath to STRING"); } - public static implicit operator RadiusConfigurationFile(string path) - { - if (path == null) - { - throw new InvalidCastException("Unable cast NULL to ConfigPath"); - } - - try - { - return new RadiusConfigurationFile(path); - } - catch (Exception ex) - { - throw new InvalidCastException("Invalid configuration path", ex); - } - } - public override string ToString() => FileName; } diff --git a/src/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj b/src/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj index ddeefd57..9a78c532 100644 --- a/src/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj +++ b/src/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj @@ -76,6 +76,7 @@ + diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config new file mode 100644 index 00000000..6aa04e25 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config new file mode 100644 index 00000000..eda452bb --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config new file mode 100644 index 00000000..74bfe02e --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config new file mode 100644 index 00000000..512f7ee7 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config new file mode 100644 index 00000000..bc964b2e --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config new file mode 100644 index 00000000..09127d3c --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config new file mode 100644 index 00000000..b1e3795e --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config new file mode 100644 index 00000000..360ae34c --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config new file mode 100644 index 00000000..ab909cdd --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config new file mode 100644 index 00000000..40ca7a74 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config new file mode 100644 index 00000000..c4fc1d5a --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config new file mode 100644 index 00000000..d14f0d34 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config new file mode 100644 index 00000000..1a31df5b --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs similarity index 80% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs index db25499d..74dda23f 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConfigs.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs @@ -1,4 +1,4 @@ -namespace MultiFactor.Radius.Adapter.Tests.E2E.Constants; +namespace Multifactor.Radius.Adapter.EndToEndTests.Constants; internal static class RadiusAdapterConfigs { diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs similarity index 90% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs index a7806dc3..9bec0530 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Constants/RadiusAdapterConstants.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs @@ -1,4 +1,4 @@ -namespace MultiFactor.Radius.Adapter.Tests.E2E.Constants; +namespace Multifactor.Radius.Adapter.EndToEndTests.Constants; internal static class RadiusAdapterConstants { diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs similarity index 74% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs index e84861f6..dd8c3777 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestBase.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs @@ -3,39 +3,30 @@ using Microsoft.Extensions.Options; using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.ConfigLoading; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Udp; using MultiFactor.Radius.Adapter.Extensions; using MultiFactor.Radius.Adapter.Infrastructure.Configuration; using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; -using MultiFactor.Radius.Adapter.Tests.E2E.Udp; -using MultiFactor.Radius.Adapter.Tests.Fixtures; -using MultiFactor.Radius.Adapter.Tests.Fixtures.ConfigLoading; -using MultiFactor.Radius.Adapter.Tests.Fixtures.Radius; -namespace MultiFactor.Radius.Adapter.Tests.E2E; +namespace Multifactor.Radius.Adapter.EndToEndTests; -public abstract class E2ETestBase : IDisposable +public abstract class E2ETestBase(RadiusFixtures radiusFixtures) : IDisposable { - private IHost? _host = null; - private readonly RadiusHostApplicationBuilder _radiusHostApplicationBuilder; - private readonly RadiusPacketParser _packetParser; - private readonly SharedSecret _secret; - private readonly UdpSocket _udpSocket; - - protected E2ETestBase(RadiusFixtures radiusFixtures) - { - _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder(new[] - { - "--environment", "Test" - }); - _packetParser = radiusFixtures.Parser; - _secret = radiusFixtures.SharedSecret; - _udpSocket = radiusFixtures.UdpSocket; - } + private IHost? _host; + private readonly RadiusHostApplicationBuilder _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder([ + "--environment", "Test" + ]); + private readonly RadiusPacketParser _packetParser = radiusFixtures.Parser; + private readonly SharedSecret? _secret = radiusFixtures.SharedSecret; + private readonly UdpSocket _udpSocket = radiusFixtures.UdpSocket; private protected async Task StartHostAsync( string rootConfigName, - string[] clientConfigFileNames = null, - string envPrefix = null, + string[]? clientConfigFileNames = null, + string? envPrefix = null, Action? configure = null) { _radiusHostApplicationBuilder.AddLogging(); @@ -69,7 +60,7 @@ private protected async Task StartHostAsync( await _host.StartAsync(); } - protected IRadiusPacket SendPacketAsync(IRadiusPacket radiusPacket) + protected IRadiusPacket SendPacketAsync(IRadiusPacket? radiusPacket) { if (radiusPacket is null) { @@ -85,9 +76,9 @@ protected IRadiusPacket SendPacketAsync(IRadiusPacket radiusPacket) return parsed; } - protected IRadiusPacket CreateRadiusPacket(PacketCode packetCode, SharedSecret secret = null) + protected IRadiusPacket? CreateRadiusPacket(PacketCode packetCode, SharedSecret? secret = null) { - IRadiusPacket packet = null; + IRadiusPacket? packet; switch (packetCode) { case PacketCode.AccessRequest: @@ -111,15 +102,15 @@ protected IRadiusPacket CreateRadiusPacket(PacketCode packetCode, SharedSecret s private void ReplaceRadiusConfigs( string rootConfigName, - string[] clientConfigFileNames = null, - string envPrefix = null) + string[]? clientConfigFileNames = null, + string? envPrefix = null) { if (string.IsNullOrEmpty(rootConfigName)) throw new ArgumentException("Empty config path"); var clientConfigs = clientConfigFileNames? .Select(fileName => TestEnvironment.GetAssetPath(TestAssetLocation.E2EBaseConfigs, fileName)) - .ToArray() ?? Array.Empty(); + .ToArray() ?? []; var rootConfig = TestEnvironment.GetAssetPath(TestAssetLocation.E2EBaseConfigs, rootConfigName); diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs similarity index 68% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs index 8519803b..a273f8fc 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/E2ETestsUtils.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs @@ -1,13 +1,12 @@ using System.Net; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using MultiFactor.Radius.Adapter.Core; using MultiFactor.Radius.Adapter.Core.Radius; using MultiFactor.Radius.Adapter.Core.Radius.Attributes; -using MultiFactor.Radius.Adapter.Tests.E2E.Udp; -using MultiFactor.Radius.Adapter.Tests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Udp; -namespace MultiFactor.Radius.Adapter.Tests.E2E; +namespace Multifactor.Radius.Adapter.EndToEndTests; internal static class E2ETestsUtils { @@ -28,18 +27,6 @@ internal static UdpSocket GetUdpSocket(string ip, int port) return new UdpSocket(IPAddress.Parse(ip), port); } - internal static T GetSensitiveData(string fileName, string sectionName) - { - var sensitiveDataPath = TestEnvironment.GetAssetPath(TestAssetLocation.E2ESensitiveData, fileName); - - IConfigurationRoot config = new ConfigurationBuilder() - .AddJsonFile(sensitiveDataPath, optional: false, reloadOnChange: true) - .Build(); - - var sensitiveData = config.GetRequiredSection(sectionName).Get(); - return sensitiveData; - } - internal static Dictionary GetSensitiveData(string fileName) { var envs = new Dictionary(); @@ -60,6 +47,11 @@ internal static string GetEnvPrefix(string envKey) if (string.IsNullOrWhiteSpace(envKey)) throw new ArgumentNullException(nameof(envKey)); var parts = envKey.Split('_'); - return parts[0] + "_"; + if (parts?.Length > 0) + { + return parts[0] + "_"; + } + + throw new ArgumentException($"Invalid env key: {envKey}"); } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs new file mode 100644 index 00000000..b9504647 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestClientConfigsProvider.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Options; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.XmlAppConfiguration; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.ConfigLoading; + +internal class TestClientConfigsProvider(IOptions options) : IClientConfigurationsProvider +{ + private readonly Dictionary _dict = new(); + private readonly TestConfigProviderOptions _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + + public RadiusAdapterConfiguration[] GetClientConfigurations() + { + var clientConfigFiles = GetFiles().ToArray(); + if (clientConfigFiles.Length == 0) + { + return []; + } + var fileSources = clientConfigFiles.Select(x => new RadiusConfigurationFile(x)).ToArray(); + foreach (var file in fileSources) + { + var config = RadiusAdapterConfigurationFactory.Create(file, file.Name, _options.EnvironmentVariablePrefix); + _dict.Add(file, config); + } + + var envVarSources = DefaultClientConfigurationsProvider.GetEnvVarClients() + .Select(x => new RadiusConfigurationEnvironmentVariable(x)) + .ExceptBy(fileSources.Select(x => RadiusConfigurationSource.TransformName(x.Name)), x => x.Name); + + foreach (var envVarClient in envVarSources) + { + var config = RadiusAdapterConfigurationFactory.Create(envVarClient, _options.EnvironmentVariablePrefix); + _dict.Add(envVarClient, config); + } + + return _dict.Select(x => x.Value).ToArray(); + } + + public RadiusConfigurationSource GetSource(RadiusAdapterConfiguration configuration) + { + return _dict.FirstOrDefault(x => x.Value == configuration).Key; + } + + private IEnumerable GetFiles() + { + if (_options.ClientConfigFilePaths.Length > 0) + { + foreach (var f in _options.ClientConfigFilePaths) + { + if (File.Exists(f)) + { + yield return f; + } + } + + yield break; + } + + if (string.IsNullOrWhiteSpace(_options.ClientConfigsFolderPath)) + { + yield break; + } + + if (!Directory.Exists(_options.ClientConfigsFolderPath)) + { + yield break; + } + + foreach (var f in Directory.GetFiles(_options.ClientConfigsFolderPath, "*.config")) + { + yield return f; + } + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs new file mode 100644 index 00000000..01032c8c --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestConfigProviderOptions.cs @@ -0,0 +1,9 @@ +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.ConfigLoading; + +internal class TestConfigProviderOptions +{ + public string? RootConfigFilePath { get; set; } + public string? ClientConfigsFolderPath { get; set; } + public string[] ClientConfigFilePaths { get; set; } = []; + public string? EnvironmentVariablePrefix { get; set; } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestRootConfigProvider.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestRootConfigProvider.cs new file mode 100644 index 00000000..784a70c5 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ConfigLoading/TestRootConfigProvider.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.XmlAppConfiguration; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.ConfigLoading; + +internal static class TestRootConfigProvider +{ + public static RadiusAdapterConfiguration GetRootConfiguration(TestConfigProviderOptions options) + { + RadiusConfigurationFile rdsRootConfig; + + if (!string.IsNullOrWhiteSpace(options.RootConfigFilePath)) + { + rdsRootConfig = new RadiusConfigurationFile(options.RootConfigFilePath); + } + else + { + var asm = Assembly.GetAssembly(typeof(RdsEntryPoint)); + if (asm is null) + { + throw new Exception("Main assembly not found"); + } + + var path = $"{asm.Location}.config"; + rdsRootConfig = new RadiusConfigurationFile(path); + } + + var config = RadiusAdapterConfigurationFactory.Create(rdsRootConfig); + return config; + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/EmptyStringsListInput.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/EmptyStringsListInput.cs new file mode 100644 index 00000000..18367374 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/EmptyStringsListInput.cs @@ -0,0 +1,19 @@ +using System.Collections; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures; + +internal class EmptyStringsListInput: IEnumerable +{ + public IEnumerator GetEnumerator() + { + yield return new object[] { string.Empty }; + yield return new object[] { " " }; + yield return new object[] { null }; + yield return new object[] { Environment.NewLine }; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/AttributesBuilder.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/AttributesBuilder.cs new file mode 100644 index 00000000..2194bd11 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/AttributesBuilder.cs @@ -0,0 +1,27 @@ +using LdapForNet; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.LdapResponse +{ + internal class AttributesBuilder(SearchResultAttributeCollection attributes) + { + private readonly SearchResultAttributeCollection _attributes = attributes ?? throw new ArgumentNullException(nameof(attributes)); + + public AttributesBuilder Add(string attribute, params string[] values) + { + if (string.IsNullOrWhiteSpace(attribute)) + { + throw new ArgumentException($"'{nameof(attribute)}' cannot be null or whitespace.", nameof(attribute)); + } + + if (values is null) + { + throw new ArgumentNullException(nameof(values)); + } + + var attr = new DirectoryAttribute { Name = attribute }; + attr.AddValues(values); + _attributes.Add(attr); + return this; + } + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/LdapEntryFactory.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/LdapEntryFactory.cs new file mode 100644 index 00000000..8c4fd431 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/LdapResponse/LdapEntryFactory.cs @@ -0,0 +1,26 @@ +using LdapForNet; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.LdapResponse +{ + internal static class LdapEntryFactory + { + public static LdapEntry Create(string dn, Action? setAttributes = null) + { + if (string.IsNullOrWhiteSpace(dn)) + { + throw new ArgumentException($"'{nameof(dn)}' cannot be null or whitespace.", nameof(dn)); + } + + var entry = new LdapEntry + { + Dn = dn, + DirectoryAttributes = new SearchResultAttributeCollection() + }; + + var builder = new AttributesBuilder(entry.DirectoryAttributes); + setAttributes?.Invoke(builder); + + return entry; + } + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs new file mode 100644 index 00000000..02a1304a --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs @@ -0,0 +1,48 @@ +using System.Security.Cryptography; +using MultiFactor.Radius.Adapter.Core.Radius; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Radius; + +internal static class RadiusPacketFactory +{ + public static IRadiusPacket? AccessRequest(SharedSecret? packetSecret = null) + { + var header = RadiusPacketHeader.Create(PacketCode.AccessRequest, 0); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); + var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); + return packet; + } + + public static IRadiusPacket? AccessChallenge(SharedSecret? packetSecret = null) + { + var header = RadiusPacketHeader.Create(PacketCode.AccessChallenge, 0); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); + var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); + return packet; + } + + public static IRadiusPacket? AccessReject(SharedSecret? packetSecret = null) + { + var header = RadiusPacketHeader.Create(PacketCode.AccessReject, 0); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); + var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); + return packet; + } + + public static IRadiusPacket? StatusServer(SharedSecret? packetSecret = null) + { + var header = RadiusPacketHeader.Create(PacketCode.StatusServer, 0); + var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); + var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); + return packet; + } + + private static byte[] GenerateSecret() + { + using var rng = RandomNumberGenerator.Create(); + var data = new byte[16]; + // Fill the salt with cryptographically strong byte values. + rng.GetNonZeroBytes(data); + return data; + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ServiceCollectionExtensions.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..d5fad5c5 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/ServiceCollectionExtensions.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures; + +internal static class ServiceCollectionExtensions +{ + public static IServiceCollection RemoveService(this IServiceCollection services) where TService : class + { + services.RemoveAll(); + return services; + } + + public static bool HasDescriptor(this IServiceCollection services) where TService : class + { + return services.FirstOrDefault(x => x.ServiceType == typeof(TService)) != null; + } + + /// + /// Replaces implementation to if the service collection contains descriptor. + /// + /// Abstraction type. + /// Implementation type. + /// Service Collection + /// for chaining. + public static IServiceCollection ReplaceService(this IServiceCollection services) + where TService : class where TImplementation : class, TService + { + var descriptor = services.SingleOrDefault(x => x.ServiceType == typeof(TService)); + if (descriptor == null) return services; + + var newDescriptor = new ServiceDescriptor(typeof(TService), typeof(TImplementation), descriptor.Lifetime); + services.Remove(descriptor); + services.Add(newDescriptor); + + return services; + } + + /// + /// Replaces implementation to the concrete instance of if the service collection contains descriptor. + /// + /// Abstraction type. + /// Service Collection. + /// Implementation instanbce. + /// for chaining. + public static IServiceCollection ReplaceService(this IServiceCollection services, TService instance) + where TService : class + { + var descriptor = services.SingleOrDefault(x => x.ServiceType == typeof(TService)); + if (descriptor == null) return services; + + var newDescriptor = new ServiceDescriptor(typeof(TService), instance); + services.Remove(descriptor); + services.Add(newDescriptor); + + return services; + } + + /// + /// Replaces implementation to the concrete instance of created by the specified factory if the service collection contains descriptor. + /// + /// Abstraction type + /// Service Collection. + /// Implementation instance factory. + /// for chaining. + public static IServiceCollection ReplaceService(this IServiceCollection services, Func factory) + where TService : class + { + var descriptor = services.SingleOrDefault(x => x.ServiceType == typeof(TService)); + if (descriptor == null) return services; + + var newDescriptor = new ServiceDescriptor(typeof(TService), factory, descriptor.Lifetime); + services.Remove(descriptor); + services.Add(newDescriptor); + + return services; + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironment.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironment.cs new file mode 100644 index 00000000..50cf86e9 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironment.cs @@ -0,0 +1,39 @@ +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures; + +internal enum TestAssetLocation +{ + RootDirectory, + ClientsDirectory, + E2EBaseConfigs, + E2ESensitiveData +} + +internal static class TestEnvironment +{ + private static readonly string AppFolder = $"{Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)}{Path.DirectorySeparatorChar}"; + private static readonly string AssetsFolder = $"{AppFolder}Assets"; + + public static string GetAssetPath(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) return AssetsFolder; + return $"{AssetsFolder}{Path.DirectorySeparatorChar}{fileName}"; + } + + public static string GetAssetPath(TestAssetLocation location) + { + return location switch + { + TestAssetLocation.ClientsDirectory => $"{AssetsFolder}{Path.DirectorySeparatorChar}clients", + TestAssetLocation.E2EBaseConfigs => $"{AssetsFolder}{Path.DirectorySeparatorChar}BaseConfigs", + TestAssetLocation.E2ESensitiveData => $"{AssetsFolder}{Path.DirectorySeparatorChar}SensitiveData", + _ => AssetsFolder, + }; + } + + public static string GetAssetPath(TestAssetLocation location, string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) return GetAssetPath(location); + var s = $"{GetAssetPath(location)}{Path.DirectorySeparatorChar}{Path.Combine(fileName.Split('/', '\\'))}"; + return s; + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironmentVariables.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironmentVariables.cs new file mode 100644 index 00000000..80781783 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/TestEnvironmentVariables.cs @@ -0,0 +1,71 @@ +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures; + +/// +/// Wraps +/// +internal class TestEnvironmentVariables +{ + private readonly HashSet _names; + + private TestEnvironmentVariables(HashSet names) + { + _names = names; + } + + public static void With(Action action) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + var names = new HashSet(); + + action(new TestEnvironmentVariables(names)); + + foreach (var name in names) + { + Environment.SetEnvironmentVariable(name, null); + } + } + + public static async Task With(Func action) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + var names = new HashSet(); + + await action(new TestEnvironmentVariables(names)); + + foreach (var name in names) + { + Environment.SetEnvironmentVariable(name, null); + } + } + + public TestEnvironmentVariables SetEnvironmentVariable(string name, string value) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"'{nameof(name)}' cannot be null or whitespace.", nameof(name)); + } + + _names.Add(name); + Environment.SetEnvironmentVariable(name, value); + + return this; + } + + public TestEnvironmentVariables SetEnvironmentVariables(Dictionary dictionary) + { + foreach (var env in dictionary) + { + SetEnvironmentVariable(env.Key, env.Value); + } + + return this; + } +} diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj b/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj new file mode 100644 index 00000000..b5e47984 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj @@ -0,0 +1,37 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + ..\libs\LdapForNet.dll + + + + + + + + + + + + + Always + + + diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/RadiusFixtures.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/RadiusFixtures.cs new file mode 100644 index 00000000..2d016407 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/RadiusFixtures.cs @@ -0,0 +1,26 @@ +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Udp; + +namespace Multifactor.Radius.Adapter.EndToEndTests; + +public class RadiusFixtures : IDisposable +{ + public RadiusPacketParser Parser { get; } = E2ETestsUtils.GetRadiusPacketParser(); + + public UdpSocket UdpSocket { get; } = E2ETestsUtils.GetUdpSocket( + RadiusAdapterConstants.LocalHost, + RadiusAdapterConstants.DefaultRadiusAdapterPort); + + public SharedSecret? SharedSecret { get; } = new(RadiusAdapterConstants.DefaultSharedSecret); + + public void Dispose() + { + UdpSocket.Dispose(); + } +} + +[CollectionDefinition("Radius e2e")] +public class RadiusFixturesCollection : ICollectionFixture +{ +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs new file mode 100644 index 00000000..262ba29e --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs @@ -0,0 +1,217 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Infrastructure.Http; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Dto; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class BypassWhenApiUnreachableTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Fact] + public async Task BST001_ShouldAccept() + { + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await StartHostAsync( + "root-no-bypass-when-api-unreachable.config", + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + [Fact] + public async Task BST002_ShouldAccept() + { + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + + await StartHostAsync( + "root-bypass-true-when-api-unreachable.config", + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + [Fact] + public async Task BST003_ShouldAccept() + { + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new MultifactorApiUnreachableException()); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + + await StartHostAsync( + "root-no-bypass-when-api-unreachable.config", + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + [Fact] + public async Task BST004_ShouldReject() + { + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new MultifactorApiUnreachableException()); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await StartHostAsync( + "root-bypass-false-when-api-unreachable.config", + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + } + + [Fact] + public async Task BST005_ShouldAccept() + { + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new MultifactorApiUnreachableException()); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + + await StartHostAsync( + "root-bypass-true-when-api-unreachable.config", + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + [Fact] + public async Task BST006_ShouldAccept() + { + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await StartHostAsync( + "root-bypass-false-when-api-unreachable.config", + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs similarity index 80% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs index e731529b..1934a1a0 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/AccessRequestTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs @@ -1,23 +1,19 @@ using MultiFactor.Radius.Adapter.Core.Radius; -using MultiFactor.Radius.Adapter.Tests.E2E.Constants; -using MultiFactor.Radius.Adapter.Tests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; -namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; [Collection("Radius e2e")] -public class AccessRequestTests : E2ETestBase +public class DefaultUsersBindTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) { - public AccessRequestTests(RadiusFixtures radiusFixtures) : base(radiusFixtures) - { - } - [Fact] public async Task SendAuthRequestWithoutCredentials_ShouldReject() { - await StartHostAsync(RadiusAdapterConfigs.RootConfig, new[] { RadiusAdapterConfigs.AccessRequestConfig }); + await StartHostAsync(RadiusAdapterConfigs.RootConfig, [RadiusAdapterConfigs.AccessRequestConfig]); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest.AddAttributes(new Dictionary() + accessRequest!.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); var response = SendPacketAsync(accessRequest); @@ -41,11 +37,11 @@ await TestEnvironmentVariables.With(async env => await StartHostAsync( RadiusAdapterConfigs.RootConfig, - new[] { RadiusAdapterConfigs.AccessRequestConfig }, + [RadiusAdapterConfigs.AccessRequestConfig], envPrefix: prefix); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest.AddAttributes(new Dictionary() + accessRequest!.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, { "User-Name", RadiusAdapterConstants.BindUserName }, @@ -74,11 +70,11 @@ await TestEnvironmentVariables.With(async env => await StartHostAsync( RadiusAdapterConfigs.RootConfig, - new[] { RadiusAdapterConfigs.AccessRequestConfig }, + [RadiusAdapterConfigs.AccessRequestConfig], envPrefix: prefix); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest.AddAttributes(new Dictionary() + accessRequest!.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, { "User-Name", RadiusAdapterConstants.AdminUserName }, @@ -107,11 +103,11 @@ await TestEnvironmentVariables.With(async env => await StartHostAsync( RadiusAdapterConfigs.RootConfig, - new[] { RadiusAdapterConfigs.AccessRequestConfig }, + [RadiusAdapterConfigs.AccessRequestConfig], envPrefix: prefix); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest.AddAttributes(new Dictionary() + accessRequest!.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, { "User-Name", RadiusAdapterConstants.ChangePasswordUserName }, diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs new file mode 100644 index 00000000..3e0169dd --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs @@ -0,0 +1,104 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class MultipleActiveDirectory2FaGroupsTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST012_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-multiple-active-directory-2fa-groups.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST013_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-multiple-not-existed-active-directory-2fa-groups.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs new file mode 100644 index 00000000..e1e74871 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs @@ -0,0 +1,104 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class MultipleActiveDirectoryGroupsTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST009_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-multiple-active-directory-groups.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST010_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-multiple-not-existed-active-directory-groups.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + }); + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs new file mode 100644 index 00000000..04f419f2 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs @@ -0,0 +1,59 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class SingleActiveDirectory2FaGroupTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST011_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-single-active-directory-2fa-group.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs new file mode 100644 index 00000000..113379ec --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs @@ -0,0 +1,104 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class SingleActiveDirectoryGroupTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST007_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-active-directory-group.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST008_ShouldReject(string configName) + { + var sensitiveData = + E2ETestsUtils.GetSensitiveData(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-not-existed-active-directory-group.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + }); + } +} \ No newline at end of file diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs similarity index 53% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs index d0cd08e4..676b9c61 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Tests/StatusServerTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs @@ -1,23 +1,19 @@ using MultiFactor.Radius.Adapter.Core.Radius; -using MultiFactor.Radius.Adapter.Tests.E2E.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; -namespace MultiFactor.Radius.Adapter.Tests.E2E.Tests; +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; [Collection("Radius e2e")] -public class StatusServerTests : E2ETestBase +public class StatusServerTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) { - public StatusServerTests(RadiusFixtures radiusFixtures) : base(radiusFixtures) - { - } - [Fact] public async Task GetServerStatus_ShouldSuccess() { - await StartHostAsync(RadiusAdapterConfigs.RootConfig, new[] { RadiusAdapterConfigs.StatusServerConfig }); + await StartHostAsync(RadiusAdapterConfigs.RootConfig, [RadiusAdapterConfigs.StatusServerConfig]); var serverStatusPacket = CreateRadiusPacket(PacketCode.StatusServer); - serverStatusPacket.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); + serverStatusPacket!.AddAttributes(new Dictionary() { { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier } }); var response = SendPacketAsync(serverStatusPacket); diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Udp/UdpData.cs similarity index 88% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/Udp/UdpData.cs index 5e8a3967..ac2120d1 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpData.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Udp/UdpData.cs @@ -1,6 +1,6 @@ using System.Text; -namespace MultiFactor.Radius.Adapter.Tests.E2E.Udp; +namespace Multifactor.Radius.Adapter.EndToEndTests.Udp; public record UdpData { diff --git a/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Udp/UdpSocket.cs similarity index 95% rename from src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs rename to src/Multifactor.Radius.Adapter.EndToEndTests/Udp/UdpSocket.cs index a3ce3f10..3d084b91 100644 --- a/src/MultiFactor.Radius.Adapter.Tests/E2E/Udp/UdpSocket.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Udp/UdpSocket.cs @@ -2,7 +2,7 @@ using System.Net.Sockets; using System.Text; -namespace MultiFactor.Radius.Adapter.Tests.E2E.Udp; +namespace Multifactor.Radius.Adapter.EndToEndTests.Udp; public class UdpSocket : IDisposable { diff --git a/src/multifactor-radius-adapter.sln b/src/multifactor-radius-adapter.sln index 8b6a2a28..f291794a 100644 --- a/src/multifactor-radius-adapter.sln +++ b/src/multifactor-radius-adapter.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiFactor.Radius.Adapter. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiFactor.Radius.Adapter", "MultiFactor.Radius.Adapter\MultiFactor.Radius.Adapter.csproj", "{8C663BCC-03FE-437C-A81E-1B581BF2BD3D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multifactor.Radius.Adapter.EndToEndTests", "Multifactor.Radius.Adapter.EndToEndTests\Multifactor.Radius.Adapter.EndToEndTests.csproj", "{23295204-C87C-433C-A5A7-2085E961E126}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,6 +31,10 @@ Global {8C663BCC-03FE-437C-A81E-1B581BF2BD3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {8C663BCC-03FE-437C-A81E-1B581BF2BD3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C663BCC-03FE-437C-A81E-1B581BF2BD3D}.Release|Any CPU.Build.0 = Release|Any CPU + {23295204-C87C-433C-A5A7-2085E961E126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23295204-C87C-433C-A5A7-2085E961E126}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23295204-C87C-433C-A5A7-2085E961E126}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23295204-C87C-433C-A5A7-2085E961E126}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From b0c856d1ca420e2799c68a6641893abf5696f525 Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:56:19 +0700 Subject: [PATCH 5/9] Feature/dev 551 552 configuration (#70) * DEV-551 * DEV-552 * e2e configuration via code --- .../ServiceConfigurationFactory.cs | 3 +- ...adiusFirstFactorAuthenticationProcessor.cs | 1 - .../BaseConfigs/root-first-factor.config | 16 ++ ...e-active-directory-2fa-bypass-group.config | 21 ++ ...d-active-directory-2fa-bypass-group.config | 21 ++ .../Constants/RadiusAdapterConfigs.cs | 10 - .../E2EClientConfigurationsProvider.cs | 26 ++ .../E2ETestBase.cs | 34 +++ .../E2ETestsUtils.cs | 29 +- .../Fixtures/Models/ConfigSensitiveData.cs | 34 +++ .../Fixtures/Models/E2ERadiusConfiguration.cs | 11 + .../Models/RadiusConfigurationModel.cs | 16 ++ .../Tests/DefaultUsersBindTests.cs | 270 +++++++++++++----- .../Tests/FirstFactorTests.cs | 106 +++++++ .../MultipleActiveDirectory2FaGroupsTests.cs | 4 +- .../MultipleActiveDirectoryGroupsTests.cs | 4 +- ...ingleActiveDirectory2FaBypassGroupTests.cs | 104 +++++++ .../SingleActiveDirectory2FaGroupTests.cs | 2 +- .../Tests/SingleActiveDirectoryGroupTests.cs | 4 +- .../Tests/StatusServerTests.cs | 20 +- 20 files changed, 636 insertions(+), 100 deletions(-) create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/E2EClientConfigurationsProvider.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/ConfigSensitiveData.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/E2ERadiusConfiguration.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/RadiusConfigurationModel.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs diff --git a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ServiceConfigurationFactory.cs b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ServiceConfigurationFactory.cs index add10071..27f6d10c 100644 --- a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ServiceConfigurationFactory.cs +++ b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/ConfigurationLoading/ServiceConfigurationFactory.cs @@ -43,8 +43,7 @@ public IServiceConfiguration CreateConfig(RadiusAdapterConfiguration rootConfigu var apiUrlSetting = appSettings.MultifactorApiUrl; var apiProxySetting = appSettings.MultifactorApiProxy; var apiTimeoutSetting = appSettings.MultifactorApiTimeout; - - + if (string.IsNullOrEmpty(apiUrlSetting)) { throw InvalidConfigurationException.For(x => x.AppSettings.MultifactorApiUrl, diff --git a/src/MultiFactor.Radius.Adapter/Server/Pipeline/FirstFactorAuthentication/Processing/RadiusFirstFactorAuthenticationProcessor.cs b/src/MultiFactor.Radius.Adapter/Server/Pipeline/FirstFactorAuthentication/Processing/RadiusFirstFactorAuthenticationProcessor.cs index a5152009..7a0085a3 100644 --- a/src/MultiFactor.Radius.Adapter/Server/Pipeline/FirstFactorAuthentication/Processing/RadiusFirstFactorAuthenticationProcessor.cs +++ b/src/MultiFactor.Radius.Adapter/Server/Pipeline/FirstFactorAuthentication/Processing/RadiusFirstFactorAuthenticationProcessor.cs @@ -100,7 +100,6 @@ private async Task ProcessRadiusAuthAsync(RadiusContext context) else { _logger.LogWarning("Remote Radius Server did not respond on message with id={id}", requestPacket.Header.Identifier); - context.Flags.SkipResponse(); return PacketCode.AccessReject; } } diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config new file mode 100644 index 00000000..8f656718 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config new file mode 100644 index 00000000..9d284390 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config new file mode 100644 index 00000000..b7ed8a14 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs deleted file mode 100644 index 74dda23f..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConfigs.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Multifactor.Radius.Adapter.EndToEndTests.Constants; - -internal static class RadiusAdapterConfigs -{ - public const string StatusServerConfig = "status-server.config"; - - public const string AccessRequestConfig = "access-request.config"; - - public const string RootConfig = "root.config"; -} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/E2EClientConfigurationsProvider.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2EClientConfigurationsProvider.cs new file mode 100644 index 00000000..d26d05d3 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2EClientConfigurationsProvider.cs @@ -0,0 +1,26 @@ +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.XmlAppConfiguration; + +namespace Multifactor.Radius.Adapter.EndToEndTests; + +public class E2EClientConfigurationsProvider : IClientConfigurationsProvider +{ + private readonly Dictionary _clientConfigurations; + + public E2EClientConfigurationsProvider(Dictionary? clientConfigurations) + { + _clientConfigurations = clientConfigurations ?? new Dictionary(); + } + + public RadiusConfigurationSource GetSource(RadiusAdapterConfiguration configuration) + { + return new RadiusConfigurationModel(_clientConfigurations.FirstOrDefault(x => x.Value == configuration).Key); + } + + public RadiusAdapterConfiguration[] GetClientConfigurations() + { + return _clientConfigurations.Select(x => x.Value).ToArray(); + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs index dd8c3777..59945daf 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs @@ -5,11 +5,13 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.ConfigLoading; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Udp; using MultiFactor.Radius.Adapter.Extensions; using MultiFactor.Radius.Adapter.Infrastructure.Configuration; using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; namespace Multifactor.Radius.Adapter.EndToEndTests; @@ -59,6 +61,38 @@ private protected async Task StartHostAsync( await _host.StartAsync(); } + + private protected async Task StartHostAsync( + RadiusAdapterConfiguration rootConfig, + Dictionary? clientConfigs = null, + Action? configure = null) + { + _radiusHostApplicationBuilder.AddLogging(); + + _radiusHostApplicationBuilder.Services.ReplaceService(prov => + { + var factory = prov.GetRequiredService(); + + var config = factory.CreateConfig(rootConfig); + config.Validate(); + + return config; + }); + + var clientConfigsProvider = new E2EClientConfigurationsProvider(clientConfigs); + + _radiusHostApplicationBuilder.Services.ReplaceService(clientConfigsProvider); + + _radiusHostApplicationBuilder.AddMiddlewares(); + + _radiusHostApplicationBuilder.ConfigureApplication(); + + configure?.Invoke(_radiusHostApplicationBuilder); + + _host = _radiusHostApplicationBuilder.Build(); + + await _host.StartAsync(); + } protected IRadiusPacket SendPacketAsync(IRadiusPacket? radiusPacket) { diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs index a273f8fc..353025fc 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs @@ -4,6 +4,7 @@ using MultiFactor.Radius.Adapter.Core.Radius; using MultiFactor.Radius.Adapter.Core.Radius.Attributes; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; using Multifactor.Radius.Adapter.EndToEndTests.Udp; namespace Multifactor.Radius.Adapter.EndToEndTests; @@ -27,7 +28,7 @@ internal static UdpSocket GetUdpSocket(string ip, int port) return new UdpSocket(IPAddress.Parse(ip), port); } - internal static Dictionary GetSensitiveData(string fileName) + internal static Dictionary GetEnvironmentVariables(string fileName) { var envs = new Dictionary(); var sensitiveDataPath = TestEnvironment.GetAssetPath(TestAssetLocation.E2ESensitiveData, fileName); @@ -41,6 +42,32 @@ internal static Dictionary GetSensitiveData(string fileName) return envs; } + + internal static ConfigSensitiveData[] GetConfigSensitiveData(string fileName) + { + var sensitiveDataPath = TestEnvironment.GetAssetPath(TestAssetLocation.E2ESensitiveData, fileName); + + var lines = File.ReadLines(sensitiveDataPath); + var sensitiveData = new List(); + + foreach (var line in lines) + { + var parts = line.Split('_'); + var data = sensitiveData.FirstOrDefault(x => x.ConfigName == parts[0].Trim()); + if (data != null) + { + data.AddConfigValue(parts[1].Trim(), parts[2].Trim()); + } + else + { + var newElement = new ConfigSensitiveData(parts[0].Trim()); + newElement.AddConfigValue(parts[1].Trim(), parts[2].Trim()); + sensitiveData.Add(newElement); + } + } + + return sensitiveData.ToArray(); + } internal static string GetEnvPrefix(string envKey) { diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/ConfigSensitiveData.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/ConfigSensitiveData.cs new file mode 100644 index 00000000..3a79ac21 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/ConfigSensitiveData.cs @@ -0,0 +1,34 @@ +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; + +public class ConfigSensitiveData +{ + public string ConfigName { get; } + public Dictionary Data { get; } + + public ConfigSensitiveData(string configName, Dictionary data) + { + ConfigName = configName; + Data = data; + } + + public ConfigSensitiveData(string configName) + { + ConfigName = configName; + Data = new Dictionary(); + } + + public void AddConfigValue(string key, string? value) + { + Data.Add(key, value); + } +} + +public static class ConfigSensitiveDataExtensions +{ + public static string? GetConfigValue(this ConfigSensitiveData[] configs, string configName, string fieldName) + { + var config = configs.First(x => x.ConfigName == configName); + config.Data.TryGetValue(fieldName, out string? value); + return value; + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/E2ERadiusConfiguration.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/E2ERadiusConfiguration.cs new file mode 100644 index 00000000..ce207e13 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/E2ERadiusConfiguration.cs @@ -0,0 +1,11 @@ +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; + +public class E2ERadiusConfiguration( + RadiusAdapterConfiguration rootConfig, + Dictionary? clientConfigs = null) +{ + public RadiusAdapterConfiguration RootConfiguration { get; } = rootConfig; + public Dictionary? ClientConfigs { get; } = clientConfigs; +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/RadiusConfigurationModel.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/RadiusConfigurationModel.cs new file mode 100644 index 00000000..aae56c21 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Models/RadiusConfigurationModel.cs @@ -0,0 +1,16 @@ +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.XmlAppConfiguration; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; + +public class RadiusConfigurationModel : RadiusConfigurationSource +{ + public override string Name { get; } + + public RadiusConfigurationModel(string name) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + + Name = name; + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs index 1934a1a0..31d0da40 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/DefaultUsersBindTests.cs @@ -1,16 +1,54 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; -using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; [Collection("Radius e2e")] public class DefaultUsersBindTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) { - [Fact] - public async Task SendAuthRequestWithoutCredentials_ShouldReject() + [Theory] + [InlineData("root.ad.env")] + [InlineData("root.radius.env")] + public async Task SendAuthRequestWithoutCredentials_RootConfig_ShouldReject(string configName) { - await StartHostAsync(RadiusAdapterConfigs.RootConfig, [RadiusAdapterConfigs.AccessRequestConfig]); + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName); + + var clientConfigName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)) + } + }; + + await StartHostAsync(rootConfig); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); accessRequest!.AddAttributes(new Dictionary() @@ -23,101 +61,177 @@ public async Task SendAuthRequestWithoutCredentials_ShouldReject() } [Theory] - [InlineData("ad.env")] - public async Task SendAuthRequestWithBindUser_ShouldAccept(string configName) + [InlineData("root.ad.env")] + [InlineData("root.radius.env")] + public async Task SendAuthRequest_RootConfig_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetConfigSensitiveData(configName); - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); - - await TestEnvironmentVariables.With(async env => + var clientConfigName = "root"; + var rootConfig = new RadiusAdapterConfiguration() { - env.SetEnvironmentVariables(sensitiveData); - - await StartHostAsync( - RadiusAdapterConfigs.RootConfig, - [RadiusAdapterConfigs.AccessRequestConfig], - envPrefix: prefix); - - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() + AppSettings = new AppSettingsSection() { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); - - var response = SendPacketAsync(accessRequest); + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)) + } + }; + + await StartHostAsync(rootConfig); - Assert.NotNull(response); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } - + [Theory] - [InlineData("ad.env")] - public async Task SendAuthRequestWithAdminUser_ShouldAccept(string configName) + [InlineData("client.ad.env")] + [InlineData("client.radius.env")] + public async Task SendAuthRequestWithBindUser_ClientConfig_ShouldAccept(string configName) { - var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); - - await TestEnvironmentVariables.With(async env => + var config = CreateRadiusConfiguration(configName); + await StartHostAsync(config.RootConfiguration, config.ClientConfigs); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); - - await StartHostAsync( - RadiusAdapterConfigs.RootConfig, - [RadiusAdapterConfigs.AccessRequestConfig], - envPrefix: prefix); - - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.AdminUserName }, - { "User-Password", RadiusAdapterConstants.AdminUserPassword } - }); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - var response = SendPacketAsync(accessRequest); + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } - Assert.NotNull(response); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + [Theory] + [InlineData("client.ad.env")] + [InlineData("client.radius.env")] + public async Task SendAuthRequestWithAdminUser_ClientConfig_ShouldAccept(string configName) + { + var config = CreateRadiusConfiguration(configName); + await StartHostAsync(config.RootConfiguration, config.ClientConfigs); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.AdminUserName }, + { "User-Password", RadiusAdapterConstants.AdminUserPassword } }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } - + [Theory] - [InlineData("ad.env")] - public async Task SendAuthRequestWithPasswordUser_ShouldAccept(string configName) + [InlineData("client.ad.env")] + [InlineData("client.radius.env")] + public async Task SendAuthRequestWithPasswordUser_ClientConfig_ShouldAccept(string configName) + { + var config = CreateRadiusConfiguration(configName); + await StartHostAsync(config.RootConfiguration, config.ClientConfigs); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.ChangePasswordUserName }, + { "User-Password", RadiusAdapterConstants.ChangePasswordUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + private E2ERadiusConfiguration CreateRadiusConfiguration(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); - - await TestEnvironmentVariables.With(async env => + E2ETestsUtils.GetConfigSensitiveData(configName); + + var rootConfig = new RadiusAdapterConfiguration() { - env.SetEnvironmentVariables(sensitiveData); - - await StartHostAsync( - RadiusAdapterConfigs.RootConfig, - [RadiusAdapterConfigs.AccessRequestConfig], - envPrefix: prefix); - - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() + AppSettings = new AppSettingsSection() { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.ChangePasswordUserName }, - { "User-Password", RadiusAdapterConstants.ChangePasswordUserPassword } - }); + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug" + } + }; - var response = SendPacketAsync(accessRequest); + var clientConfigName = "client"; + var clientConfigs = new Dictionary() + { + { + clientConfigName, new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + clientConfigName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)) + } + } + } + }; - Assert.NotNull(response); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + return new E2ERadiusConfiguration(rootConfig, clientConfigs); } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs new file mode 100644 index 00000000..615cda4e --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs @@ -0,0 +1,106 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class FirstFactorTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST016_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetEnvironmentVariables(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-first-factor.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST017_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetEnvironmentVariables(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var mfApiMock = new Mock(); + + mfApiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfApiMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-first-factor.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", "Bad-Password" } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Empty(mfApiMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + }); + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs index 3e0169dd..0ed39a52 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs @@ -17,7 +17,7 @@ public class MultipleActiveDirectory2FaGroupsTests(RadiusFixtures radiusFixtures public async Task BST012_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); @@ -62,7 +62,7 @@ await StartHostAsync( public async Task BST013_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs index e1e74871..354243f4 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs @@ -17,7 +17,7 @@ public class MultipleActiveDirectoryGroupsTests(RadiusFixtures radiusFixtures) : public async Task BST009_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); @@ -62,7 +62,7 @@ await StartHostAsync( public async Task BST010_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs new file mode 100644 index 00000000..2e111e58 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs @@ -0,0 +1,104 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class SingleActiveDirectory2FaBypassGroupTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST014_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetEnvironmentVariables(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-single-active-directory-2fa-bypass-group.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } + + [Theory] + [InlineData("ad-root-conf.env")] + public async Task BST015_ShouldAccept(string configName) + { + var sensitiveData = + E2ETestsUtils.GetEnvironmentVariables(configName); + + var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + + var secondFactorMock = new Mock(); + + secondFactorMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(secondFactorMock.Object); + }; + + await TestEnvironmentVariables.With(async env => + { + env.SetEnvironmentVariables(sensitiveData); + + await StartHostAsync( + "root-single-not-existed-active-directory-2fa-bypass-group.config", + envPrefix: prefix, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + }); + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs index 04f419f2..582074da 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs @@ -17,7 +17,7 @@ public class SingleActiveDirectory2FaGroupTests(RadiusFixtures radiusFixtures) : public async Task BST011_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs index 113379ec..ded41eb4 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs @@ -17,7 +17,7 @@ public class SingleActiveDirectoryGroupTests(RadiusFixtures radiusFixtures) : E2 public async Task BST007_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); @@ -62,7 +62,7 @@ await StartHostAsync( public async Task BST008_ShouldReject(string configName) { var sensitiveData = - E2ETestsUtils.GetSensitiveData(configName); + E2ETestsUtils.GetEnvironmentVariables(configName); var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs index 676b9c61..22024309 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/StatusServerTests.cs @@ -1,5 +1,7 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; @@ -9,7 +11,23 @@ public class StatusServerTests(RadiusFixtures radiusFixtures) : E2ETestBase(radi [Fact] public async Task GetServerStatus_ShouldSuccess() { - await StartHostAsync(RadiusAdapterConfigs.RootConfig, [RadiusAdapterConfigs.StatusServerConfig]); + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + FirstFactorAuthenticationSource = "None" + } + }; + + await StartHostAsync(rootConfig); var serverStatusPacket = CreateRadiusPacket(PacketCode.StatusServer); From 6864df3d3662fdef4f94b91a1c17aa94417ef607 Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:17:32 +0700 Subject: [PATCH 6/9] In code tests configuration --- .../Assets/BaseConfigs/access-request.config | 14 --- .../root-active-directory-group.config | 21 ---- ...t-bypass-false-when-api-unreachable.config | 13 -- ...ot-bypass-true-when-api-unreachable.config | 13 -- .../BaseConfigs/root-first-factor.config | 16 --- ...ultiple-active-directory-2fa-groups.config | 21 ---- ...ot-multiple-active-directory-groups.config | 21 ---- ...existed-active-directory-2fa-groups.config | 21 ---- ...not-existed-active-directory-groups.config | 21 ---- ...root-no-bypass-when-api-unreachable.config | 12 -- ...-not-existed-active-directory-group.config | 19 --- ...e-active-directory-2fa-bypass-group.config | 21 ---- ...t-single-active-directory-2fa-group.config | 21 ---- ...d-active-directory-2fa-bypass-group.config | 21 ---- .../Assets/BaseConfigs/root.config | 8 -- .../Assets/BaseConfigs/status-server.config | 13 -- .../Tests/BypassWhenApiUnreachableTests.cs | 49 ++++++-- .../Tests/FirstFactorTests.cs | 119 +++++++++++------- .../MultipleActiveDirectory2FaGroupsTests.cs | 111 +++++++++------- .../MultipleActiveDirectoryGroupsTests.cs | 111 +++++++++------- ...ingleActiveDirectory2FaBypassGroupTests.cs | 111 +++++++++------- .../SingleActiveDirectory2FaGroupTests.cs | 71 +++++++---- .../Tests/SingleActiveDirectoryGroupTests.cs | 111 +++++++++------- 23 files changed, 429 insertions(+), 530 deletions(-) delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config delete mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config deleted file mode 100644 index 6aa04e25..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/access-request.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config deleted file mode 100644 index eda452bb..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-active-directory-group.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config deleted file mode 100644 index 74bfe02e..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-false-when-api-unreachable.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config deleted file mode 100644 index 512f7ee7..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-bypass-true-when-api-unreachable.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config deleted file mode 100644 index 8f656718..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-first-factor.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config deleted file mode 100644 index bc964b2e..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-2fa-groups.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config deleted file mode 100644 index 09127d3c..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-active-directory-groups.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config deleted file mode 100644 index b1e3795e..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-2fa-groups.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config deleted file mode 100644 index 360ae34c..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-multiple-not-existed-active-directory-groups.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config deleted file mode 100644 index ab909cdd..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-no-bypass-when-api-unreachable.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config deleted file mode 100644 index 40ca7a74..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-not-existed-active-directory-group.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config deleted file mode 100644 index 9d284390..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-bypass-group.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config deleted file mode 100644 index c4fc1d5a..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-active-directory-2fa-group.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config deleted file mode 100644 index b7ed8a14..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root-single-not-existed-active-directory-2fa-bypass-group.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config deleted file mode 100644 index d14f0d34..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/root.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config b/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config deleted file mode 100644 index 1a31df5b..00000000 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Assets/BaseConfigs/status-server.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs index 262ba29e..555159f8 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/BypassWhenApiUnreachableTests.cs @@ -4,6 +4,7 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Infrastructure.Http; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Dto; @@ -28,9 +29,9 @@ public async Task BST001_ShouldAccept() builder.Services.ReplaceService(secondFactorMock.Object); }; - await StartHostAsync( - "root-no-bypass-when-api-unreachable.config", - configure: hostConfiguration); + var rootConfig = CreateRootConfig(); + + await StartHostAsync(rootConfig, configure: hostConfiguration); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); accessRequest!.AddAttributes(new Dictionary() @@ -61,9 +62,10 @@ public async Task BST002_ShouldAccept() builder.Services.ReplaceService(secondFactorMock.Object); }; - + var rootConfig = CreateRootConfig(true); + await StartHostAsync( - "root-bypass-true-when-api-unreachable.config", + rootConfig, configure: hostConfiguration); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); @@ -95,9 +97,10 @@ public async Task BST003_ShouldAccept() builder.Services.ReplaceService(secondFactorMock.Object); }; - + var rootConfig = CreateRootConfig(); + await StartHostAsync( - "root-no-bypass-when-api-unreachable.config", + rootConfig, configure: hostConfiguration); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); @@ -129,8 +132,10 @@ public async Task BST004_ShouldReject() builder.Services.ReplaceService(secondFactorMock.Object); }; + var rootConfig = CreateRootConfig(false); + await StartHostAsync( - "root-bypass-false-when-api-unreachable.config", + rootConfig, configure: hostConfiguration); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); @@ -162,9 +167,10 @@ public async Task BST005_ShouldAccept() builder.Services.ReplaceService(secondFactorMock.Object); }; - + var rootConfig = CreateRootConfig(true); + await StartHostAsync( - "root-bypass-true-when-api-unreachable.config", + rootConfig, configure: hostConfiguration); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); @@ -196,8 +202,10 @@ public async Task BST006_ShouldAccept() builder.Services.ReplaceService(secondFactorMock.Object); }; + var rootConfig = CreateRootConfig(false); + await StartHostAsync( - "root-bypass-false-when-api-unreachable.config", + rootConfig, configure: hostConfiguration); var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); @@ -214,4 +222,23 @@ await StartHostAsync( Assert.Single(secondFactorMock.Invocations); Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } + + private RadiusAdapterConfiguration CreateRootConfig(bool? bypassSecondFactorWhenApiUnreachable = null) + { + return new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + FirstFactorAuthenticationSource = "None", + BypassSecondFactorWhenApiUnreachable = bypassSecondFactorWhenApiUnreachable ?? true + } + }; + } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs index 615cda4e..1fa79803 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/FirstFactorTests.cs @@ -4,6 +4,8 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; @@ -18,9 +20,7 @@ public class FirstFactorTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiu public async Task BST016_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var mfAPiMock = new Mock(); @@ -32,41 +32,35 @@ public async Task BST016_ShouldAccept(string configName) { builder.Services.ReplaceService(mfAPiMock.Object); }; - - await TestEnvironmentVariables.With(async env => - { - env.SetEnvironmentVariables(sensitiveData); - await StartHostAsync( - "root-first-factor.config", - envPrefix: prefix, - configure: hostConfiguration); + var rootConfig = CreateRadiusConfiguration(sensitiveData); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + await StartHostAsync( + rootConfig, + configure: hostConfiguration); - var response = SendPacketAsync(accessRequest); - - Assert.NotNull(response); - Assert.Single(mfAPiMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } - + [Theory] [InlineData("ad-root-conf.env")] [InlineData("radius-root-conf.env")] public async Task BST017_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var mfApiMock = new Mock(); @@ -78,29 +72,62 @@ public async Task BST017_ShouldAccept(string configName) { builder.Services.ReplaceService(mfApiMock.Object); }; - - await TestEnvironmentVariables.With(async env => + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", "Bad-Password" } + }); - await StartHostAsync( - "root-first-factor.config", - envPrefix: prefix, - configure: hostConfiguration); + var response = SendPacketAsync(accessRequest); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", "Bad-Password" } - }); + Assert.NotNull(response); + Assert.Empty(mfApiMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + } - var response = SendPacketAsync(accessRequest); + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)) + } + }; - Assert.NotNull(response); - Assert.Empty(mfApiMock.Invocations); - Assert.Equal(PacketCode.AccessReject, response.Header.Code); - }); + return rootConfig; } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs index 0ed39a52..2c4d415e 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectory2FaGroupsTests.cs @@ -4,6 +4,8 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; @@ -17,9 +19,7 @@ public class MultipleActiveDirectory2FaGroupsTests(RadiusFixtures radiusFixtures public async Task BST012_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -32,39 +32,33 @@ public async Task BST012_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => - { - env.SetEnvironmentVariables(sensitiveData); + var rootConfig = CreateRadiusConfiguration(sensitiveData, "Not-Existed-Group;E2E"); - await StartHostAsync( - "root-multiple-active-directory-2fa-groups.config", - envPrefix: prefix, - configure: hostConfiguration); + await StartHostAsync( + rootConfig, + configure: hostConfiguration); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - var response = SendPacketAsync(accessRequest); + var response = SendPacketAsync(accessRequest); - Assert.NotNull(response); - Assert.Single(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } - + [Theory] [InlineData("ad-root-conf.env")] public async Task BST013_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -77,28 +71,57 @@ public async Task BST013_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => + var rootConfig = CreateRadiusConfiguration(sensitiveData, "Not-Existed-Group-1;Not-Existed-Group-2"); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - await StartHostAsync( - "root-multiple-not-existed-active-directory-2fa-groups.config", - envPrefix: prefix, - configure: hostConfiguration); + var response = SendPacketAsync(accessRequest); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } - var response = SendPacketAsync(accessRequest); + private RadiusAdapterConfiguration CreateRadiusConfiguration( + ConfigSensitiveData[] sensitiveData, + string activeDirectory2FaGroups) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ActiveDirectory2faGroup = activeDirectory2FaGroups + } + }; - Assert.NotNull(response); - Assert.Empty(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + return rootConfig; } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs index 354243f4..ace518cb 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/MultipleActiveDirectoryGroupsTests.cs @@ -4,6 +4,8 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; @@ -17,9 +19,7 @@ public class MultipleActiveDirectoryGroupsTests(RadiusFixtures radiusFixtures) : public async Task BST009_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -32,39 +32,33 @@ public async Task BST009_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => - { - env.SetEnvironmentVariables(sensitiveData); + var rootConfig = CreateRadiusConfiguration(sensitiveData, "Not-Existed-Group;E2E"); - await StartHostAsync( - "root-multiple-active-directory-groups.config", - envPrefix: prefix, - configure: hostConfiguration); + await StartHostAsync( + rootConfig, + configure: hostConfiguration); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - var response = SendPacketAsync(accessRequest); + var response = SendPacketAsync(accessRequest); - Assert.NotNull(response); - Assert.Single(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } - + [Theory] [InlineData("ad-root-conf.env")] public async Task BST010_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -77,28 +71,57 @@ public async Task BST010_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => + var rootConfig = CreateRadiusConfiguration(sensitiveData, "Not-Existed-Group-1;Not-Existed-Group-2"); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - await StartHostAsync( - "root-multiple-not-existed-active-directory-groups.config", - envPrefix: prefix, - configure: hostConfiguration); + var response = SendPacketAsync(accessRequest); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + } - var response = SendPacketAsync(accessRequest); + private RadiusAdapterConfiguration CreateRadiusConfiguration( + ConfigSensitiveData[] sensitiveData, + string activeDirectoryGroups) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ActiveDirectoryGroup = activeDirectoryGroups + } + }; - Assert.NotNull(response); - Assert.Empty(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessReject, response.Header.Code); - }); + return rootConfig; } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs index 2e111e58..9ff718b6 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaBypassGroupTests.cs @@ -4,6 +4,8 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; @@ -17,9 +19,7 @@ public class SingleActiveDirectory2FaBypassGroupTests(RadiusFixtures radiusFixtu public async Task BST014_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -32,39 +32,33 @@ public async Task BST014_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => - { - env.SetEnvironmentVariables(sensitiveData); + var rootConfig = CreateRadiusConfiguration(sensitiveData, "E2E"); - await StartHostAsync( - "root-single-active-directory-2fa-bypass-group.config", - envPrefix: prefix, - configure: hostConfiguration); + await StartHostAsync( + rootConfig, + configure: hostConfiguration); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - var response = SendPacketAsync(accessRequest); + var response = SendPacketAsync(accessRequest); - Assert.NotNull(response); - Assert.Empty(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } - + [Theory] [InlineData("ad-root-conf.env")] public async Task BST015_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -77,28 +71,57 @@ public async Task BST015_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => + var rootConfig = CreateRadiusConfiguration(sensitiveData, "Not-Existed-Group"); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - await StartHostAsync( - "root-single-not-existed-active-directory-2fa-bypass-group.config", - envPrefix: prefix, - configure: hostConfiguration); + var response = SendPacketAsync(accessRequest); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } - var response = SendPacketAsync(accessRequest); + private RadiusAdapterConfiguration CreateRadiusConfiguration( + ConfigSensitiveData[] sensitiveData, + string activeDirectory2FaBypassGroup) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ActiveDirectory2faBypassGroup = activeDirectory2FaBypassGroup + } + }; - Assert.NotNull(response); - Assert.Single(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + return rootConfig; } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs index 582074da..7319790d 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectory2FaGroupTests.cs @@ -4,6 +4,8 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; @@ -17,10 +19,8 @@ public class SingleActiveDirectory2FaGroupTests(RadiusFixtures radiusFixtures) : public async Task BST011_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); - + E2ETestsUtils.GetConfigSensitiveData(configName); + var secondFactorMock = new Mock(); secondFactorMock @@ -31,29 +31,58 @@ public async Task BST011_ShouldAccept(string configName) { builder.Services.ReplaceService(secondFactorMock.Object); }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData, "E2E"); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); - await TestEnvironmentVariables.With(async env => + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - await StartHostAsync( - "root-single-active-directory-2fa-group.config", - envPrefix: prefix, - configure: hostConfiguration); + var response = SendPacketAsync(accessRequest); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + private RadiusAdapterConfiguration CreateRadiusConfiguration( + ConfigSensitiveData[] sensitiveData, + string groups) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", - var response = SendPacketAsync(accessRequest); + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), - Assert.NotNull(response); - Assert.Single(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ActiveDirectory2faGroup = groups + } + }; + + return rootConfig; } } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs index ded41eb4..ea0d90a8 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/SingleActiveDirectoryGroupTests.cs @@ -4,6 +4,8 @@ using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Constants; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; using MultiFactor.Radius.Adapter.Services.MultiFactorApi; using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; @@ -17,9 +19,7 @@ public class SingleActiveDirectoryGroupTests(RadiusFixtures radiusFixtures) : E2 public async Task BST007_ShouldAccept(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -32,29 +32,25 @@ public async Task BST007_ShouldAccept(string configName) builder.Services.ReplaceService(secondFactorMock.Object); }; - await TestEnvironmentVariables.With(async env => - { - env.SetEnvironmentVariables(sensitiveData); + var rootConfig = CreateRadiusConfiguration(sensitiveData, "E2E"); - await StartHostAsync( - "root-active-directory-group.config", - envPrefix: prefix, - configure: hostConfiguration); + await StartHostAsync( + rootConfig, + configure: hostConfiguration); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - var response = SendPacketAsync(accessRequest); + var response = SendPacketAsync(accessRequest); - Assert.NotNull(response); - Assert.Single(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessAccept, response.Header.Code); - }); + Assert.NotNull(response); + Assert.Single(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } [Theory] @@ -62,9 +58,7 @@ await StartHostAsync( public async Task BST008_ShouldReject(string configName) { var sensitiveData = - E2ETestsUtils.GetEnvironmentVariables(configName); - - var prefix = E2ETestsUtils.GetEnvPrefix(sensitiveData.First().Key); + E2ETestsUtils.GetConfigSensitiveData(configName); var secondFactorMock = new Mock(); @@ -76,29 +70,58 @@ public async Task BST008_ShouldReject(string configName) { builder.Services.ReplaceService(secondFactorMock.Object); }; - - await TestEnvironmentVariables.With(async env => + + var rootConfig = CreateRadiusConfiguration(sensitiveData, "Not-Existed-Group"); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() { - env.SetEnvironmentVariables(sensitiveData); + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); - await StartHostAsync( - "root-not-existed-active-directory-group.config", - envPrefix: prefix, - configure: hostConfiguration); + var response = SendPacketAsync(accessRequest); - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); - accessRequest!.AddAttributes(new Dictionary() - { - { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, - { "User-Name", RadiusAdapterConstants.BindUserName }, - { "User-Password", RadiusAdapterConstants.BindUserPassword } - }); + Assert.NotNull(response); + Assert.Empty(secondFactorMock.Invocations); + Assert.Equal(PacketCode.AccessReject, response.Header.Code); + } - var response = SendPacketAsync(accessRequest); + private RadiusAdapterConfiguration CreateRadiusConfiguration( + ConfigSensitiveData[] sensitiveData, + string activeDirectoryGroup) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ActiveDirectoryGroup = activeDirectoryGroup + } + }; - Assert.NotNull(response); - Assert.Empty(secondFactorMock.Invocations); - Assert.Equal(PacketCode.AccessReject, response.Header.Code); - }); + return rootConfig; } } \ No newline at end of file From e444c65bf97c74b29148a6cf99bc174ca16d027f Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Thu, 20 Mar 2025 09:35:47 +0700 Subject: [PATCH 7/9] BST018-BST022 tests (#72) --- .../Properties/launchSettings.json | 4 +- .../Constants/RadiusAdapterConstants.cs | 6 +- .../Dockerfile | 6 + .../E2ETestBase.cs | 92 +++++++- .../E2ETestsUtils.cs | 4 +- .../Fixtures/Radius/RadiusPacketFactory.cs | 16 +- ...factor.Radius.Adapter.EndToEndTests.csproj | 12 +- .../Tests/AccessChallengeTests.cs | 204 ++++++++++++++++++ .../Tests/ChangePasswordTests.cs | 201 +++++++++++++++++ .../Tests/PreSecondFactorTests.cs | 124 +++++++++++ src/multifactor-radius-adapter.sln | 3 +- src/testenvironments.json | 10 + 12 files changed, 652 insertions(+), 30 deletions(-) create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Dockerfile create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ChangePasswordTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs create mode 100644 src/testenvironments.json diff --git a/src/MultiFactor.Radius.Adapter/Properties/launchSettings.json b/src/MultiFactor.Radius.Adapter/Properties/launchSettings.json index 90dc75e7..960eb168 100644 --- a/src/MultiFactor.Radius.Adapter/Properties/launchSettings.json +++ b/src/MultiFactor.Radius.Adapter/Properties/launchSettings.json @@ -2,7 +2,6 @@ "profiles": { "MultiFactor.Radius.Adapter": { "commandName": "Project", - "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, @@ -12,8 +11,7 @@ "commandName": "Docker", "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "localhost", - "LD_DEBUG": "libs" + "ASPNETCORE_ENVIRONMENT": "localhost" }, "publishAllPorts": true, "useSSL": false diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs index 9bec0530..003820a4 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Constants/RadiusAdapterConstants.cs @@ -7,12 +7,12 @@ internal static class RadiusAdapterConstants public const string DefaultSharedSecret = "000"; public const string DefaultNasIdentifier = "e2e"; - public const string BindUserName = "BindUser"; + public const string BindUserName = "E2EBindUser"; public const string BindUserPassword = "Qwerty123!"; - public const string AdminUserName = "e2eAdmin"; + public const string AdminUserName = "E2EAdminUser"; public const string AdminUserPassword = "Qwerty123!"; - public const string ChangePasswordUserName = "PasswordUser"; + public const string ChangePasswordUserName = "E2EPasswordUser"; public const string ChangePasswordUserPassword = "Qwerty123!"; } \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Dockerfile b/src/Multifactor.Radius.Adapter.EndToEndTests/Dockerfile new file mode 100644 index 00000000..34a48536 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Dockerfile @@ -0,0 +1,6 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +RUN apt-get update && apt-get install -y libldap-2.5-0 +RUN ln -s libldap-2.5.so.0 /usr/lib/x86_64-linux-gnu/libldap.so.2 +WORKDIR /app +EXPOSE 80 +EXPOSE 443 \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs index 59945daf..d1bb9219 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestBase.cs @@ -1,23 +1,34 @@ +using System.Text; +using LdapForNet; +using LdapForNet.Native; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using MultiFactor.Radius.Adapter.Core.Framework; using MultiFactor.Radius.Adapter.Core.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.ConfigLoading; -using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Radius; using Multifactor.Radius.Adapter.EndToEndTests.Udp; using MultiFactor.Radius.Adapter.Extensions; using MultiFactor.Radius.Adapter.Infrastructure.Configuration; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ClientLevel; using MultiFactor.Radius.Adapter.Infrastructure.Configuration.ConfigurationLoading; using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.RootLevel; +using MultiFactor.Radius.Adapter.Services.Ldap; +using MultiFactor.Radius.Adapter.Services.Ldap.Connection; +using MultiFactor.Radius.Adapter.Services.Ldap.Profile; namespace Multifactor.Radius.Adapter.EndToEndTests; public abstract class E2ETestBase(RadiusFixtures radiusFixtures) : IDisposable { private IHost? _host; + private ProfileLoader? _profileLoader; + private ClientConfigurationFactory _clientConfigurationFactory; + private readonly RadiusHostApplicationBuilder _radiusHostApplicationBuilder = RadiusHost.CreateApplicationBuilder([ "--environment", "Test" ]); @@ -58,7 +69,10 @@ private protected async Task StartHostAsync( configure?.Invoke(_radiusHostApplicationBuilder); _host = _radiusHostApplicationBuilder.Build(); - + + _profileLoader = _host.Services.GetService(); + _clientConfigurationFactory = _host.Services.GetService(); + await _host.StartAsync(); } @@ -90,7 +104,10 @@ private protected async Task StartHostAsync( configure?.Invoke(_radiusHostApplicationBuilder); _host = _radiusHostApplicationBuilder.Build(); - + + _profileLoader = _host.Services.GetService(); + _clientConfigurationFactory = _host.Services.GetService(); + await _host.StartAsync(); } @@ -110,22 +127,22 @@ protected IRadiusPacket SendPacketAsync(IRadiusPacket? radiusPacket) return parsed; } - protected IRadiusPacket? CreateRadiusPacket(PacketCode packetCode, SharedSecret? secret = null) + protected IRadiusPacket? CreateRadiusPacket(PacketCode packetCode, SharedSecret? secret = null, byte identifier = 0) { IRadiusPacket? packet; switch (packetCode) { case PacketCode.AccessRequest: - packet = RadiusPacketFactory.AccessRequest(secret ?? _secret); + packet = RadiusPacketFactory.AccessRequest(secret ?? _secret, identifier); break; case PacketCode.StatusServer: - packet = RadiusPacketFactory.StatusServer(secret ?? _secret); + packet = RadiusPacketFactory.StatusServer(secret ?? _secret, identifier); break; case PacketCode.AccessChallenge: - packet = RadiusPacketFactory.AccessChallenge(secret ?? _secret); + packet = RadiusPacketFactory.AccessChallenge(secret ?? _secret, identifier); break; case PacketCode.AccessReject: - packet = RadiusPacketFactory.AccessReject(secret ?? _secret); + packet = RadiusPacketFactory.AccessReject(secret ?? _secret, identifier); break; default: throw new NotImplementedException(); @@ -133,7 +150,66 @@ protected IRadiusPacket SendPacketAsync(IRadiusPacket? radiusPacket) return packet; } + + protected async Task SetAttributeForUserInCatalogAsync( + string userName, + RadiusAdapterConfiguration config, + string attributeName, + object attributeValue) + { + var clientConfiguration = CreateClientConfiguration(config); + + var user = LdapIdentity.ParseUser(userName); + using var connection = LdapConnectionAdapter.CreateAsTechnicalAccAsync( + clientConfiguration.ActiveDirectoryDomain, + clientConfiguration, + NullLogger.Instance); + + var formatter = new BindIdentityFormatter(clientConfiguration); + var serviceUser = LdapIdentity.ParseUser(clientConfiguration.ServiceAccountUser); + await connection.BindAsync(formatter.FormatIdentity(serviceUser, clientConfiguration.ActiveDirectoryDomain), clientConfiguration.ServiceAccountPassword); + + var profile = await _profileLoader.LoadAsync(clientConfiguration, connection, user); + var isFreeIpa = clientConfiguration.IsFreeIpa && clientConfiguration.FirstFactorAuthenticationSource != AuthenticationSource.ActiveDirectory; + var request = BuildModifyRequest(profile.DistinguishedName, attributeName, attributeValue, isFreeIpa); + var response = await connection.SendRequestAsync(request); + + if (response.ResultCode != Native.ResultCode.Success) + { + throw new Exception($"Failed to set attribute: {response.ResultCode}"); + } + } + + protected IClientConfiguration CreateClientConfiguration(RadiusAdapterConfiguration configuration) + { + var serviceConfig = _host.Services.GetService(); + return _clientConfigurationFactory.CreateConfig("e2e", configuration, serviceConfig); + } + + private ModifyRequest BuildModifyRequest( + string dn, + string attributeName, + object attributeValue, + bool isFreeIpa) + { + var attrName = attributeName; + + var attribute = new DirectoryModificationAttribute + { + Name = attrName, + LdapModOperation = Native.LdapModOperation.LDAP_MOD_REPLACE + }; + + var bytes = Encoding.UTF8.GetBytes(attributeValue.ToString()); + if (isFreeIpa) + attribute.Add(bytes); + else + attribute.Add(bytes); + + return new ModifyRequest(dn, attribute); + } + private void ReplaceRadiusConfigs( string rootConfigName, string[]? clientConfigFileNames = null, diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs index 353025fc..92feffa3 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/E2ETestsUtils.cs @@ -43,7 +43,7 @@ internal static Dictionary GetEnvironmentVariables(string fileNa return envs; } - internal static ConfigSensitiveData[] GetConfigSensitiveData(string fileName) + internal static ConfigSensitiveData[] GetConfigSensitiveData(string fileName, string separator = "_") { var sensitiveDataPath = TestEnvironment.GetAssetPath(TestAssetLocation.E2ESensitiveData, fileName); @@ -52,7 +52,7 @@ internal static ConfigSensitiveData[] GetConfigSensitiveData(string fileName) foreach (var line in lines) { - var parts = line.Split('_'); + var parts = line.Split(separator); var data = sensitiveData.FirstOrDefault(x => x.ConfigName == parts[0].Trim()); if (data != null) { diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs index 02a1304a..de4c15ac 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Fixtures/Radius/RadiusPacketFactory.cs @@ -5,33 +5,33 @@ namespace Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Radius; internal static class RadiusPacketFactory { - public static IRadiusPacket? AccessRequest(SharedSecret? packetSecret = null) + public static IRadiusPacket? AccessRequest(SharedSecret? packetSecret = null, byte identifier = 0) { - var header = RadiusPacketHeader.Create(PacketCode.AccessRequest, 0); + var header = RadiusPacketHeader.Create(PacketCode.AccessRequest, identifier); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } - public static IRadiusPacket? AccessChallenge(SharedSecret? packetSecret = null) + public static IRadiusPacket? AccessChallenge(SharedSecret? packetSecret = null, byte identifier = 0) { - var header = RadiusPacketHeader.Create(PacketCode.AccessChallenge, 0); + var header = RadiusPacketHeader.Create(PacketCode.AccessChallenge, identifier); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } - public static IRadiusPacket? AccessReject(SharedSecret? packetSecret = null) + public static IRadiusPacket? AccessReject(SharedSecret? packetSecret = null, byte identifier = 0) { - var header = RadiusPacketHeader.Create(PacketCode.AccessReject, 0); + var header = RadiusPacketHeader.Create(PacketCode.AccessReject, identifier); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; } - public static IRadiusPacket? StatusServer(SharedSecret? packetSecret = null) + public static IRadiusPacket? StatusServer(SharedSecret? packetSecret = null, byte identifier = 0) { - var header = RadiusPacketHeader.Create(PacketCode.StatusServer, 0); + var header = RadiusPacketHeader.Create(PacketCode.StatusServer, identifier); var sharedSecret = packetSecret ?? new SharedSecret(Convert.ToHexString(GenerateSecret()).ToLower()); var packet = new RadiusPacket(header, new RadiusAuthenticator(), sharedSecret); return packet; diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj b/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj index b5e47984..4e35422f 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Multifactor.Radius.Adapter.EndToEndTests.csproj @@ -7,14 +7,16 @@ false true + Linux - - + + + - - + + @@ -22,7 +24,7 @@ - + diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs new file mode 100644 index 00000000..a0e08073 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs @@ -0,0 +1,204 @@ +using System.Text; +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Server; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class AccessChallengeTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("none-root-conf.env")] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST018_ShouldAccept(string configName) + { + var state = "BST018_ShouldAccept"; + var challenge1 = "challenge-1"; + var challenge2 = "challenge-2"; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Awaiting, state)); + + mfAPiMock + .Setup(x => x.ChallengeAsync(It.IsAny(), challenge1, It.IsAny())) + .ReturnsAsync(new ChallengeResponse(AuthenticationCode.Awaiting)); + + mfAPiMock + .Setup(x => x.ChallengeAsync(It.IsAny(), challenge2, It.IsAny())) + .ReturnsAsync(new ChallengeResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var defaultRequestAttributes = new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName } + }; + + // AccessRequest step 1 + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 0); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("User-Password", RadiusAdapterConstants.BindUserPassword); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + Assert.NotEmpty(response.Attributes["State"]); + var responseState = Encoding.UTF8.GetString((byte[])response.Attributes["State"].First()); + Assert.Equal(responseState, state); + + // Challenge step 2 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 1); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", state); + accessRequest!.AddAttribute("User-Password", challenge1); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(2, mfAPiMock.Invocations.Count); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + + // Challenge step 3 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 2); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", state); + accessRequest!.AddAttribute("User-Password", challenge2); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(3, mfAPiMock.Invocations.Count); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + [Theory] + [InlineData("none-root-conf.env")] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST019_ShouldAccept(string configName) + { + var state = "BST018_ShouldAccept"; + var challenge1 = "challenge-1"; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Awaiting, state)); + + mfAPiMock + .Setup(x => x.ChallengeAsync(It.IsAny(), challenge1, It.IsAny())) + .ReturnsAsync(new ChallengeResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var defaultRequestAttributes = new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName } + }; + + // AccessRequest step 1 + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 0); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("User-Password", RadiusAdapterConstants.BindUserPassword); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + Assert.NotEmpty(response.Attributes["State"]); + var responseState = Encoding.UTF8.GetString((byte[])response.Attributes["State"].First()); + Assert.Equal(responseState, state); + + // Challenge step 2 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 1); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", state); + accessRequest!.AddAttribute("User-Password", challenge1); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(2, mfAPiMock.Invocations.Count); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)) + } + }; + + return rootConfig; + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ChangePasswordTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ChangePasswordTests.cs new file mode 100644 index 00000000..411c2209 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ChangePasswordTests.cs @@ -0,0 +1,201 @@ +using System.Text; +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Http; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Dto; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class ChangePasswordTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + private static byte _packetId = 0; + + [Theory] + [InlineData("ad-root-change-password-conf.env")] + public async Task BST020_ShouldAccept(string configName) + { + var newPassword = "Qwerty456!"; + var currentPassword = RadiusAdapterConstants.ChangePasswordUserPassword; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName, "__"); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new AccessRequestDto() { Status = RequestStatus.Granted }); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + // Password changing + await ChangePassword( + currentPassword: currentPassword, + newPassword: newPassword, + rootConfig); + + // Rollback + await ChangePassword( + currentPassword: newPassword, + newPassword: currentPassword, + rootConfig); + } + + [Theory] + [InlineData("ad-root-pre-auth-change-password-conf.env")] + public async Task BST022_ShouldAccept(string configName) + { + var newPassword = "Qwerty456!"; + var currentPassword = RadiusAdapterConstants.ChangePasswordUserPassword; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName, "__"); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new AccessRequestDto() { Status = RequestStatus.Granted }); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + // Password changing + await ChangePassword( + currentPassword: currentPassword, + newPassword: newPassword, + rootConfig); + + // Rollback + await ChangePassword( + currentPassword: newPassword, + newPassword: currentPassword, + rootConfig); + } + + private async Task ChangePassword( + string currentPassword, + string newPassword, + RadiusAdapterConfiguration rootConfig) + { + await SetAttributeForUserInCatalogAsync( + RadiusAdapterConstants.ChangePasswordUserName, + rootConfig, + "pwdLastSet", + 0); + + var defaultRequestAttributes = new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.ChangePasswordUserName } + }; + + // AccessRequest step 1 + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: _packetId++); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("User-Password", currentPassword); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + Assert.NotEmpty(response.Attributes["State"]); + var responseState = Encoding.UTF8.GetString((byte[])response.Attributes["State"].First()); + Assert.True(Guid.TryParse(responseState, out Guid state)); + var stateString = state.ToString(); + + // New Password step 2 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: _packetId++); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", stateString); + accessRequest!.AddAttribute("User-Password", newPassword); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + + // Repeat password step 3 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: _packetId++); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State",stateString); + accessRequest!.AddAttribute("User-Password", newPassword); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ServiceAccountPassword = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ServiceAccountPassword)), + + ServiceAccountUser = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ServiceAccountUser)), + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + PreAuthenticationMethod = sensitiveData.GetConfigValue(configName, nameof(AppSettingsSection.PreAuthenticationMethod)), + InvalidCredentialDelay = sensitiveData.GetConfigValue(configName, nameof(AppSettingsSection.InvalidCredentialDelay)), + } + }; + + return rootConfig; + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs new file mode 100644 index 00000000..d84a1b33 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs @@ -0,0 +1,124 @@ +using System.Text; +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Server; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class PreSecondFactorTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("none-root-conf.env")] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST021_ShouldAccept(string configName) + { + var challenge1 = "challenge-1"; + var state = "BST021_ShouldAccept"; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Awaiting, state)); + + mfAPiMock + .Setup(x => x.ChallengeAsync(It.IsAny(), challenge1, It.IsAny())) + .ReturnsAsync(new ChallengeResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var defaultRequestAttributes = new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName } + }; + + // AccessRequest step 1 + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 1); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("User-Password", RadiusAdapterConstants.BindUserPassword); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + Assert.NotEmpty(response.Attributes["State"]); + var responseState = Encoding.UTF8.GetString((byte[])response.Attributes["State"].First()); + Assert.Equal(responseState, state); + + // Challenge step 2 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", state); + accessRequest!.AddAttribute("User-Password", challenge1); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(2, mfAPiMock.Invocations.Count); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + PreAuthenticationMethod = "push", + InvalidCredentialDelay = "3", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)) + } + }; + + return rootConfig; + } +} \ No newline at end of file diff --git a/src/multifactor-radius-adapter.sln b/src/multifactor-radius-adapter.sln index f291794a..50af90b4 100644 --- a/src/multifactor-radius-adapter.sln +++ b/src/multifactor-radius-adapter.sln @@ -9,13 +9,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\LICENSE.ru.md = ..\LICENSE.ru.md ..\README.md = ..\README.md ..\README.ru.md = ..\README.ru.md + testenvironments.json = testenvironments.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiFactor.Radius.Adapter.Tests", "MultiFactor.Radius.Adapter.Tests\MultiFactor.Radius.Adapter.Tests.csproj", "{E8A7518C-A622-4343-A594-46EE5869EE96}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiFactor.Radius.Adapter", "MultiFactor.Radius.Adapter\MultiFactor.Radius.Adapter.csproj", "{8C663BCC-03FE-437C-A81E-1B581BF2BD3D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multifactor.Radius.Adapter.EndToEndTests", "Multifactor.Radius.Adapter.EndToEndTests\Multifactor.Radius.Adapter.EndToEndTests.csproj", "{23295204-C87C-433C-A5A7-2085E961E126}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multifactor.Radius.Adapter.EndToEndTests", "Multifactor.Radius.Adapter.EndToEndTests\Multifactor.Radius.Adapter.EndToEndTests.csproj", "{23295204-C87C-433C-A5A7-2085E961E126}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/testenvironments.json b/src/testenvironments.json new file mode 100644 index 00000000..46e2fb87 --- /dev/null +++ b/src/testenvironments.json @@ -0,0 +1,10 @@ +{ + "version": "1", // value must be 1 + "environments": [ + { + "name": "Linux x64", + "type": "docker", + "dockerFile": "Multifactor.Radius.Adapter.EndToEndTests\\Dockerfile" + } + ] +} \ No newline at end of file From d5fe0fe0499fd771116de53c60d738c755060af2 Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:25:07 +0700 Subject: [PATCH 8/9] BST023, BST024 (#73) --- .../Tests/AccessChallengeTests.cs | 2 +- .../Tests/PreSecondFactorTests.cs | 136 +++++++++++++++++- 2 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs index a0e08073..50dc988f 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessChallengeTests.cs @@ -105,7 +105,7 @@ await StartHostAsync( [InlineData("radius-root-conf.env")] public async Task BST019_ShouldAccept(string configName) { - var state = "BST018_ShouldAccept"; + var state = "BST019_ShouldAccept"; var challenge1 = "challenge-1"; var sensitiveData = diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs index d84a1b33..7dd73391 100644 --- a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/PreSecondFactorTests.cs @@ -22,8 +22,55 @@ public class PreSecondFactorTests(RadiusFixtures radiusFixtures) : E2ETestBase(r [InlineData("radius-root-conf.env")] public async Task BST021_ShouldAccept(string configName) { - var challenge1 = "challenge-1"; var state = "BST021_ShouldAccept"; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept, state)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var defaultRequestAttributes = new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName } + }; + + // AccessRequest step 1 + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("User-Password", RadiusAdapterConstants.BindUserPassword); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + Assert.False(response.Attributes.ContainsKey("State")); + } + + [Theory] + [InlineData("none-root-conf.env")] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST023_ShouldAccept(string configName) + { + var challenge1 = "challenge-1"; + var state = "BST023_ShouldAccept"; var sensitiveData = E2ETestsUtils.GetConfigSensitiveData(configName); @@ -56,7 +103,7 @@ await StartHostAsync( }; // AccessRequest step 1 - var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 1); + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 0); accessRequest!.AddAttributes(defaultRequestAttributes); accessRequest!.AddAttribute("User-Password", RadiusAdapterConstants.BindUserPassword); @@ -70,7 +117,7 @@ await StartHostAsync( Assert.Equal(responseState, state); // Challenge step 2 - accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 1); accessRequest!.AddAttributes(defaultRequestAttributes); accessRequest!.AddAttribute("State", state); accessRequest!.AddAttribute("User-Password", challenge1); @@ -82,6 +129,89 @@ await StartHostAsync( Assert.Equal(PacketCode.AccessAccept, response.Header.Code); } + [Theory] + [InlineData("none-root-conf.env")] + [InlineData("ad-root-conf.env")] + [InlineData("radius-root-conf.env")] + public async Task BST024_ShouldAccept(string configName) + { + var state = "BST018_ShouldAccept"; + var challenge1 = "challenge-1"; + var challenge2 = "challenge-2"; + + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Awaiting, state)); + + mfAPiMock + .Setup(x => x.ChallengeAsync(It.IsAny(), challenge1, It.IsAny())) + .ReturnsAsync(new ChallengeResponse(AuthenticationCode.Awaiting)); + + mfAPiMock + .Setup(x => x.ChallengeAsync(It.IsAny(), challenge2, It.IsAny())) + .ReturnsAsync(new ChallengeResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var defaultRequestAttributes = new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName } + }; + + // AccessRequest step 1 + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 0); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("User-Password", RadiusAdapterConstants.BindUserPassword); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + Assert.NotEmpty(response.Attributes["State"]); + var responseState = Encoding.UTF8.GetString((byte[])response.Attributes["State"].First()); + Assert.Equal(responseState, state); + + // Challenge step 2 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 1); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", state); + accessRequest!.AddAttribute("User-Password", challenge1); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(2, mfAPiMock.Invocations.Count); + Assert.Equal(PacketCode.AccessChallenge, response.Header.Code); + + // Challenge step 3 + accessRequest = CreateRadiusPacket(PacketCode.AccessRequest, identifier: 2); + accessRequest!.AddAttributes(defaultRequestAttributes); + accessRequest!.AddAttribute("State", state); + accessRequest!.AddAttribute("User-Password", challenge2); + + response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Equal(3, mfAPiMock.Invocations.Count); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + } + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData) { var configName = "root"; From 318d085d7ecc24eafb5183a6d785e98e92a72b3e Mon Sep 17 00:00:00 2001 From: Sergey <42936486+Tu-S@users.noreply.github.com> Date: Wed, 2 Apr 2025 14:51:45 +0700 Subject: [PATCH 9/9] DEV-617 (#74) --- .../RadiusReplyAttributesSection.cs | 10 ++ .../Tests/AccessRequestAttributesTests.cs | 167 ++++++++++++++++++ .../Tests/ReplyAttributesTests.cs | 113 ++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessRequestAttributesTests.cs create mode 100644 src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ReplyAttributesTests.cs diff --git a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/Models/RadiusReply/RadiusReplyAttributesSection.cs b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/Models/RadiusReply/RadiusReplyAttributesSection.cs index a6c63e18..cb39ce88 100644 --- a/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/Models/RadiusReply/RadiusReplyAttributesSection.cs +++ b/src/MultiFactor.Radius.Adapter/Infrastructure/Configuration/Models/RadiusReply/RadiusReplyAttributesSection.cs @@ -37,4 +37,14 @@ public RadiusReplyAttribute[] Elements return Array.Empty(); } } + + public RadiusReplyAttributesSection() + { + } + + public RadiusReplyAttributesSection(RadiusReplyAttribute singleElement = null, RadiusReplyAttribute[] elements = null) + { + _elements = elements; + _singleElement = singleElement; + } } diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessRequestAttributesTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessRequestAttributesTests.cs new file mode 100644 index 00000000..e42804ec --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/AccessRequestAttributesTests.cs @@ -0,0 +1,167 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models.RadiusReply; +using MultiFactor.Radius.Adapter.Infrastructure.Http; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Dto; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class AccessRequestAttributesTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("none-root-access-request-attributes.env")] + [InlineData("ad-root-access-request-attributes.env")] + [InlineData("radius-root-access-request-attributes.env")] + public async Task BST026_ShouldAcceptAndSendAttributes(string configName) + { + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName, "__"); + + var mfAPiMock = new Mock(); + CreateRequestDto payload = null; + mfAPiMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .Callback((CreateRequestDto x, BasicAuthHeaderValue y) => payload = x) + .ReturnsAsync(new AccessRequestDto() { Status = RequestStatus.Granted} ); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + Assert.NotNull(payload); + Assert.NotEmpty(payload.Email); + Assert.NotEmpty(payload.Name); + Assert.NotEmpty(payload.Phone); + } + + [Theory] + [InlineData("none-root-access-request-attributes.env", "Partial:RemoteHost")] + [InlineData("ad-root-access-request-attributes.env", "Partial:RemoteHost")] + [InlineData("radius-root-access-request-attributes.env", "Partial:RemoteHost")] + [InlineData("none-root-access-request-attributes.env", "Full")] + [InlineData("ad-root-access-request-attributes.env", "Full")] + [InlineData("radius-root-access-request-attributes.env", "Full")] + public async Task BST027_ShouldAcceptAndNotSendAttributes(string configName, string privacyMode) + { + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName, "__"); + + var mfAPiMock = new Mock(); + CreateRequestDto payload = null; + mfAPiMock + .Setup(x => x.CreateRequestAsync(It.IsAny(), It.IsAny())) + .Callback((CreateRequestDto x, BasicAuthHeaderValue y) => payload = x) + .ReturnsAsync(new AccessRequestDto() { Status = RequestStatus.Granted} ); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData, privacyMode: privacyMode); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + Assert.NotNull(payload); + Assert.Null(payload.Email); + Assert.Null(payload.Name); + Assert.Null(payload.Phone); + } + + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData, string privacyMode = null) + + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ServiceAccountPassword = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ServiceAccountPassword)), + + ServiceAccountUser = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ServiceAccountUser)), + + PhoneAttribute = "mobile", + PrivacyMode = privacyMode + }, + + RadiusReply = new RadiusReplySection() + { + Attributes = new RadiusReplyAttributesSection(singleElement: new RadiusReplyAttribute() + { Name = "Class", From = "memberOf" }) + } + }; + + return rootConfig; + } +} \ No newline at end of file diff --git a/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ReplyAttributesTests.cs b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ReplyAttributesTests.cs new file mode 100644 index 00000000..2edff5e2 --- /dev/null +++ b/src/Multifactor.Radius.Adapter.EndToEndTests/Tests/ReplyAttributesTests.cs @@ -0,0 +1,113 @@ +using Moq; +using MultiFactor.Radius.Adapter.Core.Framework; +using MultiFactor.Radius.Adapter.Core.Framework.Context; +using MultiFactor.Radius.Adapter.Core.Radius; +using Multifactor.Radius.Adapter.EndToEndTests.Constants; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures; +using Multifactor.Radius.Adapter.EndToEndTests.Fixtures.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models; +using MultiFactor.Radius.Adapter.Infrastructure.Configuration.Models.RadiusReply; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi.Models; + +namespace Multifactor.Radius.Adapter.EndToEndTests.Tests; + +[Collection("Radius e2e")] +public class ReplyAttributesTests(RadiusFixtures radiusFixtures) : E2ETestBase(radiusFixtures) +{ + [Theory] + [InlineData("none-root-reply-attributes.env")] + [InlineData("ad-root-reply-attributes.env")] + [InlineData("radius-root-reply-attributes.env")] + public async Task BST025_ShouldAcceptAndReturnAttributes(string configName) + { + var sensitiveData = + E2ETestsUtils.GetConfigSensitiveData(configName, "__"); + + var mfAPiMock = new Mock(); + + mfAPiMock + .Setup(x => x.CreateSecondFactorRequestAsync(It.IsAny())) + .ReturnsAsync(new SecondFactorResponse(AuthenticationCode.Accept)); + + var hostConfiguration = (RadiusHostApplicationBuilder builder) => + { + builder.Services.ReplaceService(mfAPiMock.Object); + }; + + var rootConfig = CreateRadiusConfiguration(sensitiveData); + + await StartHostAsync( + rootConfig, + configure: hostConfiguration); + + var accessRequest = CreateRadiusPacket(PacketCode.AccessRequest); + accessRequest!.AddAttributes(new Dictionary() + { + { "NAS-Identifier", RadiusAdapterConstants.DefaultNasIdentifier }, + { "User-Name", RadiusAdapterConstants.BindUserName }, + { "User-Password", RadiusAdapterConstants.BindUserPassword } + }); + + var response = SendPacketAsync(accessRequest); + + Assert.NotNull(response); + Assert.Single(mfAPiMock.Invocations); + Assert.Equal(PacketCode.AccessAccept, response.Header.Code); + Assert.NotEmpty(response.Attributes); + Assert.True(response.Attributes.ContainsKey("Class")); + var classAttribute = response.Attributes["Class"]; + Assert.NotEmpty(classAttribute); + } + + private RadiusAdapterConfiguration CreateRadiusConfiguration(ConfigSensitiveData[] sensitiveData) + { + var configName = "root"; + var rootConfig = new RadiusAdapterConfiguration() + { + AppSettings = new AppSettingsSection() + { + AdapterServerEndpoint = "0.0.0.0:1812", + MultifactorApiUrl = "https://api.multifactor.dev", + LoggingLevel = "Debug", + RadiusSharedSecret = RadiusAdapterConstants.DefaultSharedSecret, + RadiusClientNasIdentifier = RadiusAdapterConstants.DefaultNasIdentifier, + BypassSecondFactorWhenApiUnreachable = true, + MultifactorNasIdentifier = "nas-identifier", + MultifactorSharedSecret = "shared-secret", + + ActiveDirectoryDomain = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ActiveDirectoryDomain)), + + NpsServerEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.NpsServerEndpoint)), + + AdapterClientEndpoint = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.AdapterClientEndpoint)), + + FirstFactorAuthenticationSource = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.FirstFactorAuthenticationSource)), + + ServiceAccountPassword = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ServiceAccountPassword)), + + ServiceAccountUser = sensitiveData.GetConfigValue( + configName, + nameof(AppSettingsSection.ServiceAccountUser)) + }, + + RadiusReply = new RadiusReplySection() + { + Attributes = new RadiusReplyAttributesSection(singleElement: new RadiusReplyAttribute() + { Name = "Class", From = "memberOf" }) + } + }; + + return rootConfig; + } +} \ No newline at end of file