diff --git a/MultiFactor.Radius.Adapter/Configuration/ClientConfiguration.cs b/MultiFactor.Radius.Adapter/Configuration/ClientConfiguration.cs index 5648469..b87eb0d 100644 --- a/MultiFactor.Radius.Adapter/Configuration/ClientConfiguration.cs +++ b/MultiFactor.Radius.Adapter/Configuration/ClientConfiguration.cs @@ -2,6 +2,7 @@ //Please see licence at //https://github.com/MultifactorLab/MultiFactor.Radius.Adapter/blob/master/LICENSE.md +using MultiFactor.Radius.Adapter.Configuration.Features.PrivacyModeFeature; using MultiFactor.Radius.Adapter.Server; using System; using System.Collections.Generic; @@ -51,7 +52,7 @@ public ClientConfiguration() /// public bool BypassSecondFactorWhenApiUnreachable { get; set; } - public PrivacyMode PrivacyMode { get; set; } + public PrivacyModeDescriptor PrivacyMode { get; set; } #region ActiveDirectory Authentication settings diff --git a/MultiFactor.Radius.Adapter/Configuration/PrivacyMode.cs b/MultiFactor.Radius.Adapter/Configuration/Features/PrivacyModeFeature/PrivacyMode.cs similarity index 71% rename from MultiFactor.Radius.Adapter/Configuration/PrivacyMode.cs rename to MultiFactor.Radius.Adapter/Configuration/Features/PrivacyModeFeature/PrivacyMode.cs index d4aa52a..872619f 100644 --- a/MultiFactor.Radius.Adapter/Configuration/PrivacyMode.cs +++ b/MultiFactor.Radius.Adapter/Configuration/Features/PrivacyModeFeature/PrivacyMode.cs @@ -12,6 +12,11 @@ public enum PrivacyMode /// /// Disable all but identity /// - Full + Full, + + /// + /// Disable all but identity and specified fields. + /// + Partial } } \ No newline at end of file diff --git a/MultiFactor.Radius.Adapter/Configuration/Features/PrivacyModeFeature/PrivacyModeDescriptor.cs b/MultiFactor.Radius.Adapter/Configuration/Features/PrivacyModeFeature/PrivacyModeDescriptor.cs new file mode 100644 index 0000000..0c04ff6 --- /dev/null +++ b/MultiFactor.Radius.Adapter/Configuration/Features/PrivacyModeFeature/PrivacyModeDescriptor.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; + +namespace MultiFactor.Radius.Adapter.Configuration.Features.PrivacyModeFeature +{ + public class PrivacyModeDescriptor + { + private readonly string[] _fields; + public PrivacyMode Mode { get; } + + public bool HasField(string field) + { + if (string.IsNullOrWhiteSpace(field)) + { + return false; + } + + return _fields.Any(x => x.Equals(field, StringComparison.OrdinalIgnoreCase)); + } + + private PrivacyModeDescriptor(PrivacyMode mode, params string[] fields) + { + Mode = mode; + _fields = fields ?? throw new ArgumentNullException(nameof(fields)); + } + + public static PrivacyModeDescriptor Create(string value) + { + if (string.IsNullOrWhiteSpace(value)) return new PrivacyModeDescriptor(PrivacyMode.None); + + var mode = GetMode(value); + if (mode != PrivacyMode.Partial) return new PrivacyModeDescriptor(mode); + + var fields = GetFields(value); + return new PrivacyModeDescriptor(mode, fields); + } + + private static PrivacyMode GetMode(string value) + { + var index = value.IndexOf(':'); + if (index == -1) + { + if (!Enum.TryParse(value, true, out var parsed1)) throw new Exception("Unexpected privacy-mode value"); + return parsed1; + } + + var sub = value.Substring(0, index); + if (!Enum.TryParse(sub, true, out var parsed2)) throw new Exception("Unexpected privacy-mode value"); + + return parsed2; + } + + private static string[] GetFields(string value) + { + var index = value.IndexOf(':'); + if (index == -1 || value.Length <= index + 1) + { + return Array.Empty(); + } + + var sub = value.Substring(index + 1); + return sub.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Distinct().ToArray(); + } + } +} diff --git a/MultiFactor.Radius.Adapter/Configuration/ServiceConfiguration.cs b/MultiFactor.Radius.Adapter/Configuration/ServiceConfiguration.cs index dd02d5b..4fa5a2a 100644 --- a/MultiFactor.Radius.Adapter/Configuration/ServiceConfiguration.cs +++ b/MultiFactor.Radius.Adapter/Configuration/ServiceConfiguration.cs @@ -14,6 +14,8 @@ using System.IO; using NetTools; using System.Text.RegularExpressions; +using MultiFactor.Radius.Adapter.Configuration.Features.PrivacyModeFeature; +using MultiFactor.Radius.Adapter.Services.MultiFactorApi; namespace MultiFactor.Radius.Adapter.Configuration { @@ -288,7 +290,6 @@ public static ClientConfiguration Load(string name, IRadiusDictionary dictionary var radiusPapEncodingSetting = appSettings.Settings["radius-pap-encoding"]?.Value; var firstFactorAuthenticationSourceSettings = appSettings.Settings["first-factor-authentication-source"]?.Value; var bypassSecondFactorWhenApiUnreachableSetting = appSettings.Settings["bypass-second-factor-when-api-unreachable"]?.Value; - var privacyModeSetting = appSettings.Settings["privacy-mode"]?.Value; var multiFactorApiKeySetting = appSettings.Settings["multifactor-nas-identifier"]?.Value; var multiFactorApiSecretSetting = appSettings.Settings["multifactor-shared-secret"]?.Value; @@ -346,13 +347,13 @@ public static ClientConfiguration Load(string name, IRadiusDictionary dictionary } } - if (!string.IsNullOrEmpty(privacyModeSetting)) + try { - if (!Enum.TryParse(privacyModeSetting, true, out var privacyMode)) - { - throw new Exception("Configuration error: Can't parse 'privacy-mode' value. Must be one of: Full, None"); - } - configuration.PrivacyMode = privacyMode; + configuration.PrivacyMode = PrivacyModeDescriptor.Create(appSettings.Settings[Literals.Configuration.PrivacyMode]?.Value); + } + catch + { + throw new Exception($"Configuration error: Can't parse '{Literals.Configuration.PrivacyMode}' value. Must be one of: Full, None, Partial:Field1,Field2"); } switch (configuration.FirstFactorAuthenticationSource) diff --git a/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj b/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj index 450167f..838f808 100644 --- a/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj +++ b/MultiFactor.Radius.Adapter/MultiFactor.Radius.Adapter.csproj @@ -137,7 +137,8 @@ - + + diff --git a/MultiFactor.Radius.Adapter/Services/MultiFactorApi/Literals.cs b/MultiFactor.Radius.Adapter/Services/MultiFactorApi/Literals.cs index 1c5d80e..6e29b67 100644 --- a/MultiFactor.Radius.Adapter/Services/MultiFactorApi/Literals.cs +++ b/MultiFactor.Radius.Adapter/Services/MultiFactorApi/Literals.cs @@ -8,5 +8,10 @@ public static class RadiusCode public const string Denied = "Denied"; public const string AwaitingAuthentication = "AwaitingAuthentication"; } + + public static class Configuration + { + public const string PrivacyMode = "privacy-mode"; + } } } diff --git a/MultiFactor.Radius.Adapter/Services/MultiFactorApi/MultiFactorApiClient.cs b/MultiFactor.Radius.Adapter/Services/MultiFactorApi/MultiFactorApiClient.cs index a304c37..e52eb3e 100644 --- a/MultiFactor.Radius.Adapter/Services/MultiFactorApi/MultiFactorApiClient.cs +++ b/MultiFactor.Radius.Adapter/Services/MultiFactorApi/MultiFactorApiClient.cs @@ -10,7 +10,6 @@ using Newtonsoft.Json; using Serilog; using System; -using System.Collections.Concurrent; using System.Linq; using System.Net; using System.Text; @@ -61,7 +60,7 @@ public async Task CreateSecondFactorRequest(PendingRequest request, } //remove user information for privacy - switch (clientConfig.PrivacyMode) + switch (clientConfig.PrivacyMode.Mode) { case PrivacyMode.Full: displayName = null; @@ -70,6 +69,31 @@ public async Task CreateSecondFactorRequest(PendingRequest request, callingStationId = ""; calledStationId = null; break; + + case PrivacyMode.Partial: + if (!clientConfig.PrivacyMode.HasField("Name")) + { + displayName = null; + } + + if (!clientConfig.PrivacyMode.HasField("Email")) + { + email = null; + } + + if (!clientConfig.PrivacyMode.HasField("Phone")) + { + userPhone = null; + } + + if (!clientConfig.PrivacyMode.HasField("RemoteHost")) + { + callingStationId = ""; + } + + calledStationId = null; + + break; } //try to get authenticated client to bypass second factor if configured