Skip to content

Commit 3cbeabe

Browse files
authored
DEV-531 (#63)
### Release 04.08.2025 | Acct-Authentic attribute support for winlogon #### New - Added Acct-Authentic attribute support. If the RADIUS packet contains the Acct-Authentic attribute without a domain value, the adapter will skip loading the profile and checking groups membership. If there is no attribute in the packet, the adapter operates in normal mode. The adapter interprets the attribute values as follows: ``` 1 - Domain 2 - Local 3 - Microsoft ```
1 parent acf38ca commit 3cbeabe

6 files changed

Lines changed: 101 additions & 20 deletions

File tree

MultiFactor.Radius.Adapter/Core/IRadiusPacket.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ IDictionary<string, List<object>> Attributes
6666
}
6767

6868
string CreateUniqueKey(IPEndPoint remoteEndpoint);
69+
AccountType AccountType { get; }
6970
}
7071
}

MultiFactor.Radius.Adapter/Core/RadiusPacket.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ public string RemoteHostName
117117
public string NasIdentifier => GetString("NAS-Identifier");
118118
public string State => GetString("State");
119119

120+
public AccountType AccountType
121+
{
122+
get
123+
{
124+
var attrValue = AcctAuthentic ?? 0;
125+
return UintToAccountType(attrValue);
126+
}
127+
}
128+
120129
public string TryGetUserPassword()
121130
{
122131
var password = UserPassword;
@@ -285,5 +294,41 @@ public object Clone()
285294

286295
return packet;
287296
}
297+
298+
private uint? AcctAuthentic
299+
{
300+
get
301+
{
302+
if (Attributes.TryGetValue("Acct-Authentic", out var values))
303+
{
304+
return values.FirstOrDefault() as uint?;
305+
}
306+
307+
return null;
308+
}
309+
}
310+
311+
private static AccountType UintToAccountType(uint value)
312+
{
313+
switch (value)
314+
{
315+
case 1:
316+
return AccountType.Domain;
317+
case 2:
318+
return AccountType.Local;
319+
case 3:
320+
return AccountType.Microsoft;
321+
default:
322+
return AccountType.Domain;
323+
}
324+
}
325+
}
326+
327+
public enum AccountType
328+
{
329+
Unknown = 0,
330+
Domain = 1,
331+
Local = 2,
332+
Microsoft = 3
288333
}
289334
}

MultiFactor.Radius.Adapter/Server/FirstAuthFactorProcessing/AnonymousProcessor.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,39 @@ public AnonymousProcessor(ActiveDirectoryMembershipVerifier membershipVerifier,
3737

3838
public Task<PacketCode> ProcessFirstAuthFactorAsync(PendingRequest request)
3939
{
40-
if (request.Configuration.CheckMembership)
40+
if (request.RequestPacket.AccountType != AccountType.Domain)
4141
{
42-
// check membership without AD authentication
43-
var result = _membershipVerifier.VerifyMembership(request);
44-
var handler = new MembershipVerificationResultHandler(result);
45-
46-
handler.EnrichRequest(request);
47-
return Task.FromResult(handler.GetDecision());
42+
_logger.Information("User '{user}' used '{accountType}' account to log in. Membership check is skipped.", request.UserName, request.RequestPacket.AccountType);
4843
}
49-
50-
if (request.Configuration.UseIdentityAttribute)
44+
else
5145
{
52-
var attrs = LoadRequiredAttributes(request, request.Configuration.TwoFAIdentityAttribyte);
53-
if (!attrs.ContainsKey(request.Configuration.TwoFAIdentityAttribyte))
46+
if (request.Configuration.CheckMembership)
5447
{
55-
_logger.Warning("Attribute '{TwoFAIdentityAttribyte}' was not loaded", request.Configuration.TwoFAIdentityAttribyte);
56-
return Task.FromResult(PacketCode.AccessReject);
48+
// check membership without AD authentication
49+
var result = _membershipVerifier.VerifyMembership(request);
50+
var handler = new MembershipVerificationResultHandler(result);
51+
52+
handler.EnrichRequest(request);
53+
return Task.FromResult(handler.GetDecision());
5754
}
5855

59-
var existedAttributes = new LdapAttributes(request.Profile.LdapAttrs);
60-
existedAttributes.Replace(request.Configuration.TwoFAIdentityAttribyte, new[] { attrs[request.Configuration.TwoFAIdentityAttribyte].FirstOrDefault() });
61-
request.Profile.UpdateAttributes(existedAttributes);
56+
if (request.Configuration.UseIdentityAttribute)
57+
{
58+
var attrs = LoadRequiredAttributes(request, request.Configuration.TwoFAIdentityAttribyte);
59+
if (!attrs.ContainsKey(request.Configuration.TwoFAIdentityAttribyte))
60+
{
61+
_logger.Warning("Attribute '{TwoFAIdentityAttribyte}' was not loaded",
62+
request.Configuration.TwoFAIdentityAttribyte);
63+
return Task.FromResult(PacketCode.AccessReject);
64+
}
65+
66+
var existedAttributes = new LdapAttributes(request.Profile.LdapAttrs);
67+
existedAttributes.Replace(request.Configuration.TwoFAIdentityAttribyte,
68+
new[] { attrs[request.Configuration.TwoFAIdentityAttribyte].FirstOrDefault() });
69+
request.Profile.UpdateAttributes(existedAttributes);
70+
}
6271
}
72+
6373
return Task.FromResult(PacketCode.AccessAccept);
6474
}
6575

MultiFactor.Radius.Adapter/Server/FirstAuthFactorProcessing/RadiusFirstAuthFactorProcessor.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ public async Task<PacketCode> ProcessFirstAuthFactorAsync(PendingRequest request
4848
{
4949
return radiusResponse;
5050
}
51+
52+
if (request.RequestPacket.AccountType != AccountType.Domain)
53+
{
54+
_logger.Information("User '{user}' used '{accountType}' account to log in. Membership check is skipped.", request.UserName, request.RequestPacket.AccountType);
55+
return radiusResponse;
56+
}
5157

5258
if (request.Configuration.CheckMembership)
5359
{

MultiFactor.Radius.Adapter/Server/PendingRequest.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class PendingRequest
4040
public string ReplyMessage { get; set; }
4141

4242
public string UserName { get; private set; }
43-
public IList<string> UserGroups { get; set; }
43+
public IList<string> UserGroups { get; set; } = new List<string>();
4444

4545
public bool MustChangePassword { get; private set; }
4646
public string MustChangePasswordDomain { get; private set; }
@@ -50,8 +50,21 @@ public class PendingRequest
5050
/// <summary>
5151
/// Should use for 2FA request to MFA API.
5252
/// </summary>
53-
public string SecondFactorIdentity => Configuration.UseIdentityAttribute ? Profile.LdapAttrs.GetValue(Configuration.TwoFAIdentityAttribyte) : UserName;
54-
53+
public string SecondFactorIdentity
54+
{
55+
get
56+
{
57+
if (!Configuration.UseIdentityAttribute)
58+
return UserName;
59+
var identity = Profile.LdapAttrs.GetValue(Configuration.TwoFAIdentityAttribyte);
60+
var name = string.IsNullOrWhiteSpace(identity) && RequestPacket.AccountType != AccountType.Domain
61+
? UserName
62+
: identity;
63+
64+
return name;
65+
}
66+
}
67+
5568
public AuthenticationState AuthenticationState { get; private set; }
5669
public UserPassphrase Passphrase { get; private set; }
5770

MultiFactor.Radius.Adapter/Services/ActiveDirectory/ActiveDirectoryService.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.DirectoryServices.AccountManagement;
1313
using System.DirectoryServices.Protocols;
1414
using System.Linq;
15+
using MultiFactor.Radius.Adapter.Core;
1516

1617
namespace MultiFactor.Radius.Adapter.Services.ActiveDirectory
1718
{
@@ -85,7 +86,12 @@ public bool VerifyCredentialAndMembership(PendingRequest request)
8586
try
8687
{
8788
VerifyCredential(user, request);
88-
return VerifyMembership(request.Configuration, user, request);
89+
90+
if (request.RequestPacket.AccountType == AccountType.Domain)
91+
return VerifyMembership(request.Configuration, user, request);
92+
93+
_logger.Information("User '{user}' used '{accountType}' account to log in. Membership check is skipped.", request.UserName, request.RequestPacket.AccountType);
94+
return true;
8995
}
9096
catch (LdapException lex)
9197
{

0 commit comments

Comments
 (0)