diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index 8da95c6314..68e1f6149c 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -64,6 +64,7 @@ and are generated based on the last package release.
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index c89e801957..db9cc55f62 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -205,6 +205,10 @@
https://github.com/dotnet/runtime
f4e99f4afa445b519abcd7c5c87cbf54771614db
+
+ https://github.com/dotnet/runtime
+ f4e99f4afa445b519abcd7c5c87cbf54771614db
+
https://github.com/dotnet/runtime
f4e99f4afa445b519abcd7c5c87cbf54771614db
diff --git a/eng/Versions.props b/eng/Versions.props
index 965ed5bdd1..9c2fd0000e 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -108,6 +108,7 @@
5.0.0-rc.1.20425.1
5.0.0-rc.1.20425.1
5.0.0-rc.1.20425.1
+ 5.0.0-rc.1.20425.1
5.0.0-rc.1.20425.1
5.0.0-rc.1.20425.1
5.0.0-rc.1.20425.1
diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs
index 2e62846f8f..c2b0b0ba7f 100644
--- a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs
+++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Builder;
@@ -22,6 +23,23 @@ namespace NegotiateAuthSample
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate(options =>
{
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ /*
+ options.EnableLdap("DOMAIN.net");
+
+ options.EnableLdap(settings =>
+ {
+ // Mandatory settings
+ settings.Domain = "DOMAIN.com";
+ // Optional settings
+ settings.MachineAccountName = "machineName";
+ settings.MachineAccountPassword = "PassW0rd";
+ settings.IgnoreNestedGroups = true;
+ });
+ */
+ }
+
options.Events = new NegotiateEvents()
{
OnAuthenticationFailed = context =>
diff --git a/src/Security/Authentication/Negotiate/src/Events/LdapContext.cs b/src/Security/Authentication/Negotiate/src/Events/LdapContext.cs
new file mode 100644
index 0000000000..9e6d7a40ac
--- /dev/null
+++ b/src/Security/Authentication/Negotiate/src/Events/LdapContext.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication.Negotiate
+{
+ ///
+ /// State for the RetrieveLdapClaims event.
+ ///
+ public class LdapContext : ResultContext
+ {
+ ///
+ /// Creates a new .
+ ///
+ ///
+ ///
+ ///
+ ///
+ public LdapContext(
+ HttpContext context,
+ AuthenticationScheme scheme,
+ NegotiateOptions options,
+ LdapSettings settings)
+ : base(context, scheme, options)
+ {
+ LdapSettings = settings;
+ }
+
+ ///
+ /// The LDAP settings to use for the RetrieveLdapClaims event.
+ ///
+ public LdapSettings LdapSettings { get; }
+ }
+}
diff --git a/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs b/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs
index 0d57be28eb..88dfdf2b74 100644
--- a/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs
+++ b/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs
@@ -16,6 +16,12 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
///
public Func OnAuthenticationFailed { get; set; } = context => Task.CompletedTask;
+ ///
+ /// Invoked after the authentication before ClaimsIdentity is populated with claims retrieved through the LDAP connection.
+ /// This event is invoked when is set to true on .
+ ///
+ public Func OnRetrieveLdapClaims { get; set; } = context => Task.CompletedTask;
+
///
/// Invoked after the authentication is complete and a ClaimsIdentity has been generated.
///
@@ -31,6 +37,11 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
///
public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context);
+ ///
+ /// Invoked after the authentication before ClaimsIdentity is populated with claims retrieved through the LDAP connection.
+ ///
+ public virtual Task RetrieveLdapClaims(LdapContext context) => OnRetrieveLdapClaims(context);
+
///
/// Invoked after the authentication is complete and a ClaimsIdentity has been generated.
///
diff --git a/src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs b/src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
new file mode 100644
index 0000000000..4ddad3c5e3
--- /dev/null
+++ b/src/Security/Authentication/Negotiate/src/Internal/LdapAdapter.cs
@@ -0,0 +1,97 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.DirectoryServices.Protocols;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Authentication.Negotiate
+{
+ internal static class LdapAdapter
+ {
+ public static async Task RetrieveClaimsAsync(LdapSettings settings, ClaimsIdentity identity, ILogger logger)
+ {
+ var user = identity.Name;
+ var userAccountName = user.Substring(0, user.IndexOf('@'));
+ var distinguishedName = settings.Domain.Split('.').Select(name => $"dc={name}").Aggregate((a, b) => $"{a},{b}");
+
+ var filter = $"(&(objectClass=user)(sAMAccountName={userAccountName}))"; // This is using ldap search query language, it is looking on the server for someUser
+ var searchRequest = new SearchRequest(distinguishedName, filter, SearchScope.Subtree, null);
+ var searchResponse = (SearchResponse) await Task.Factory.FromAsync(
+ settings.LdapConnection.BeginSendRequest,
+ settings.LdapConnection.EndSendRequest,
+ searchRequest,
+ PartialResultProcessing.NoPartialResultSupport,
+ null);
+
+ if (searchResponse.Entries.Count > 0)
+ {
+ if (searchResponse.Entries.Count > 1)
+ {
+ logger.LogWarning($"More than one response received for query: {filter} with distinguished name: {distinguishedName}");
+ }
+
+ var userFound = searchResponse.Entries[0]; //Get the object that was found on ldap
+ var memberof = userFound.Attributes["memberof"]; // You can access ldap Attributes with Attributes property
+
+ foreach (var group in memberof)
+ {
+ // Example distinguished name: CN=TestGroup,DC=KERB,DC=local
+ var groupDN = $"{Encoding.UTF8.GetString((byte[])group)}";
+ var groupCN = groupDN.Split(',')[0].Substring("CN=".Length);
+
+ if (!settings.IgnoreNestedGroups)
+ {
+ GetNestedGroups(settings.LdapConnection, identity, distinguishedName, groupCN, logger);
+ }
+ else
+ {
+ AddRole(identity, groupCN);
+ }
+ }
+ }
+ else
+ {
+ logger.LogWarning($"No response received for query: {filter} with distinguished name: {distinguishedName}");
+ }
+ }
+
+ private static void GetNestedGroups(LdapConnection connection, ClaimsIdentity principal, string distinguishedName, string groupCN, ILogger logger)
+ {
+ var filter = $"(&(objectClass=group)(sAMAccountName={groupCN}))"; // This is using ldap search query language, it is looking on the server for someUser
+ var searchRequest = new SearchRequest(distinguishedName, filter, System.DirectoryServices.Protocols.SearchScope.Subtree, null);
+ var searchResponse = (SearchResponse)connection.SendRequest(searchRequest);
+
+ if (searchResponse.Entries.Count > 0)
+ {
+ if (searchResponse.Entries.Count > 1)
+ {
+ logger.LogWarning($"More than one response received for query: {filter} with distinguished name: {distinguishedName}");
+ }
+
+ var group = searchResponse.Entries[0]; //Get the object that was found on ldap
+ string name = group.DistinguishedName;
+ AddRole(principal, name);
+
+ var memberof = group.Attributes["memberof"]; // You can access ldap Attributes with Attributes property
+ if (memberof != null)
+ {
+ foreach (var member in memberof)
+ {
+ var groupDN = $"{Encoding.UTF8.GetString((byte[])member)}";
+ var nestedGroupCN = groupDN.Split(',')[0].Substring("CN=".Length);
+ GetNestedGroups(connection, principal, distinguishedName, nestedGroupCN, logger);
+ }
+ }
+ }
+ }
+
+ private static void AddRole(ClaimsIdentity identity, string role)
+ {
+ identity.AddClaim(new Claim(identity.RoleClaimType, role));
+ }
+ }
+}
diff --git a/src/Security/Authentication/Negotiate/src/Internal/NegotiateOptionsValidationStartupFilter.cs b/src/Security/Authentication/Negotiate/src/Internal/NegotiateOptionsValidationStartupFilter.cs
new file mode 100644
index 0000000000..429a57d81e
--- /dev/null
+++ b/src/Security/Authentication/Negotiate/src/Internal/NegotiateOptionsValidationStartupFilter.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Authentication.Negotiate.Internal
+{
+ internal class NegotiateOptionsValidationStartupFilter : IStartupFilter
+ {
+ private readonly string _authenticationScheme;
+
+ public NegotiateOptionsValidationStartupFilter(string authenticationScheme)
+ {
+ _authenticationScheme = authenticationScheme;
+ }
+
+ public Action Configure(Action next)
+ {
+ return builder =>
+ {
+ // Resolve NegotiateOptions on startup to trigger post configuration and bind LdapConnection if needed
+ var options = builder.ApplicationServices.GetRequiredService>().Get(_authenticationScheme);
+ next(builder);
+ };
+ }
+ }
+}
diff --git a/src/Security/Authentication/Negotiate/src/LdapSettings.cs b/src/Security/Authentication/Negotiate/src/LdapSettings.cs
new file mode 100644
index 0000000000..1e26c26c14
--- /dev/null
+++ b/src/Security/Authentication/Negotiate/src/LdapSettings.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.DirectoryServices.Protocols;
+
+namespace Microsoft.AspNetCore.Authentication.Negotiate
+{
+ ///
+ /// Options class for configuring LDAP connections on Linux
+ ///
+ public class LdapSettings
+ {
+ ///
+ /// Configure whether LDAP connection should be used to resolve claims.
+ /// This is mainly used on Linux.
+ ///
+ public bool EnableLdapClaimResolution { get; set; }
+
+ ///
+ /// The domain to use for the LDAP connection. This is a mandatory setting.
+ ///
+ ///
+ /// DOMAIN.com
+ ///
+ public string Domain { get; set; }
+
+ ///
+ /// The machine account name to use when opening the LDAP connection.
+ /// If this is not provided, the machine wide credentials of the
+ /// domain joined machine will be used.
+ ///
+ public string MachineAccountName { get; set; }
+
+ ///
+ /// The machine account password to use when opening the LDAP connection.
+ /// This must be provided if a is provided.
+ ///
+ public string MachineAccountPassword { get; set; }
+
+ ///
+ /// This option indicates whether nested groups should be ignored when
+ /// resolving Roles. The default is false.
+ ///
+ public bool IgnoreNestedGroups { get; set; }
+
+ ///
+ /// The to be used to retrieve role claims.
+ /// If no explicit connection is provided, an LDAP connection will be
+ /// automatically created based on the ,
+ /// and
+ /// options. If provided, this connection will be used and the
+ /// , and
+ /// options will not be used to create
+ /// the .
+ ///
+ public LdapConnection LdapConnection { get; set; }
+
+ public void Validate()
+ {
+ if (EnableLdapClaimResolution)
+ {
+ if (string.IsNullOrEmpty(Domain))
+ {
+ throw new ArgumentException($"{nameof(EnableLdapClaimResolution)} is set to true but {nameof(Domain)} is not set.");
+ }
+
+ if (string.IsNullOrEmpty(MachineAccountName) && !string.IsNullOrEmpty(MachineAccountPassword))
+ {
+ throw new ArgumentException($"{nameof(MachineAccountPassword)} should only be specified when {nameof(MachineAccountName)} is configured.");
+ }
+ }
+ }
+ }
+}
diff --git a/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj b/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj
index 265ffb533a..c0aac839f5 100644
--- a/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj
+++ b/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj
@@ -10,7 +10,9 @@
+
+
diff --git a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs
index f5bbf8cbc8..e47417e170 100644
--- a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs
+++ b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs
@@ -4,6 +4,8 @@
using System;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Negotiate;
+using Microsoft.AspNetCore.Authentication.Negotiate.Internal;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@@ -52,6 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureNegotiateOptions>());
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(new NegotiateOptionsValidationStartupFilter(authenticationScheme)));
return builder.AddScheme(authenticationScheme, displayName, configureOptions);
}
}
diff --git a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs
index 835542a42d..0ef6697857 100644
--- a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs
+++ b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Principal;
using System.Text.Encodings.Web;
@@ -324,10 +325,37 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
user = new ClaimsPrincipal(new ClaimsIdentity(identity));
}
- var authenticatedContext = new AuthenticatedContext(Context, Scheme, Options)
+ AuthenticatedContext authenticatedContext;
+
+ if (Options.LdapSettings.EnableLdapClaimResolution)
{
- Principal = user
- };
+ var ldapContext = new LdapContext(Context, Scheme, Options, Options.LdapSettings)
+ {
+ Principal = user
+ };
+
+ await Events.RetrieveLdapClaims(ldapContext);
+
+ if (ldapContext.Result != null)
+ {
+ return ldapContext.Result;
+ }
+
+ await LdapAdapter.RetrieveClaimsAsync(ldapContext.LdapSettings, ldapContext.Principal.Identity as ClaimsIdentity, Logger);
+
+ authenticatedContext = new AuthenticatedContext(Context, Scheme, Options)
+ {
+ Principal = ldapContext.Principal
+ };
+ }
+ else
+ {
+ authenticatedContext = new AuthenticatedContext(Context, Scheme, Options)
+ {
+ Principal = user
+ };
+ }
+
await Events.Authenticated(authenticatedContext);
if (authenticatedContext.Result != null)
diff --git a/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs b/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs
index 3f5d36b39f..40d090265c 100644
--- a/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs
+++ b/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+
namespace Microsoft.AspNetCore.Authentication.Negotiate
{
///
@@ -33,6 +35,42 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
///
public bool PersistNtlmCredentials { get; set; } = true;
+ ///
+ /// Configuration settings for LDAP connections used to retrieve claims.
+ /// This should only be used on Linux systems.
+ ///
+ internal LdapSettings LdapSettings { get; } = new LdapSettings();
+
+ ///
+ /// Use LDAP connections used to retrieve claims for the given domain.
+ /// This should only be used on Linux systems.
+ ///
+ public void EnableLdap(string domain)
+ {
+ if (string.IsNullOrEmpty(domain))
+ {
+ throw new ArgumentNullException(nameof(domain));
+ }
+
+ LdapSettings.EnableLdapClaimResolution = true;
+ LdapSettings.Domain = domain;
+ }
+
+ ///
+ /// Use LDAP connections used to retrieve claims using the configured settings.
+ /// This should only be used on Linux systems.
+ ///
+ public void EnableLdap(Action configureSettings)
+ {
+ if (configureSettings == null)
+ {
+ throw new ArgumentNullException(nameof(configureSettings));
+ }
+
+ LdapSettings.EnableLdapClaimResolution = true;
+ configureSettings(LdapSettings);
+ }
+
///
/// Indicates if integrated server Windows Auth is being used instead of this handler.
/// See .
diff --git a/src/Security/Authentication/Negotiate/src/PostConfigureNegotiateOptions.cs b/src/Security/Authentication/Negotiate/src/PostConfigureNegotiateOptions.cs
index 91384c3293..4fb8a29be4 100644
--- a/src/Security/Authentication/Negotiate/src/PostConfigureNegotiateOptions.cs
+++ b/src/Security/Authentication/Negotiate/src/PostConfigureNegotiateOptions.cs
@@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.DirectoryServices.Protocols;
using System.Linq;
+using System.Net;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -59,6 +61,36 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
+ " Enable Windows Authentication for the server and the Negotiate Authentication handler will defer to it.");
}
}
+
+ var ldapSettings = options.LdapSettings;
+
+ if (ldapSettings.EnableLdapClaimResolution)
+ {
+ ldapSettings.Validate();
+
+ if (ldapSettings.LdapConnection == null)
+ {
+ var di = new LdapDirectoryIdentifier(server: ldapSettings.Domain, fullyQualifiedDnsHostName: true, connectionless: false);
+
+ if (string.IsNullOrEmpty(ldapSettings.MachineAccountName))
+ {
+ // Use default credentials
+ ldapSettings.LdapConnection = new LdapConnection(di);
+ }
+ else
+ {
+ // Use specific specific machine account
+ var machineAccount = ldapSettings.MachineAccountName + "@" + ldapSettings.Domain;
+ var credentials = new NetworkCredential(machineAccount, ldapSettings.MachineAccountPassword);
+ ldapSettings.LdapConnection = new LdapConnection(di, credentials);
+ }
+
+ ldapSettings.LdapConnection.SessionOptions.ProtocolVersion = 3; //Setting LDAP Protocol to latest version
+ ldapSettings.LdapConnection.Timeout = TimeSpan.FromMinutes(1);
+ }
+
+ ldapSettings.LdapConnection.Bind(); // This line actually makes the connection.
+ }
}
}
}
diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs
index 0979640207..af471a5450 100644
--- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs
+++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Reflection.Metadata;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
@@ -13,7 +12,6 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
-using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
@@ -371,6 +369,27 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
Assert.Equal(1, callCount);
}
+ [Fact]
+ public async Task OnRetrieveLdapClaims_DoesNotFireWhenLdapDisabled()
+ {
+ var callCount = 0;
+ using var host = await CreateHostAsync(options =>
+ {
+ options.Events = new NegotiateEvents()
+ {
+ OnRetrieveLdapClaims = context =>
+ {
+ callCount++;
+ return Task.CompletedTask;
+ }
+ };
+ });
+ var server = host.GetTestServer();
+
+ await KerberosStage1And2Auth(server, new TestConnection());
+ Assert.Equal(0, callCount);
+ }
+
private static async Task KerberosStage1And2Auth(TestServer server, TestConnection testConnection)
{
await KerberosStage1Auth(server, testConnection);
diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/LdapSettingsValidationTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/LdapSettingsValidationTests.cs
new file mode 100644
index 0000000000..6a706d820b
--- /dev/null
+++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/LdapSettingsValidationTests.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication.Negotiate.Test
+{
+ public class LdapSettingsValidationTests
+ {
+ [Fact]
+ public void EnabledWithoutDomainThrows()
+ {
+ var settings = new LdapSettings
+ {
+ EnableLdapClaimResolution = true
+ };
+
+ Assert.Throws(() => settings.Validate());
+ }
+
+ [Fact]
+ public void AccountPasswordWithoutAccountNameThrows()
+ {
+ var settings = new LdapSettings
+ {
+ EnableLdapClaimResolution = true,
+ MachineAccountPassword = "Passw0rd"
+ };
+
+ Assert.Throws(() => settings.Validate());
+ }
+ }
+}
diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs
index efd513b829..f038c7ca80 100644
--- a/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs
+++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/ServerDeferralTests.cs
@@ -29,8 +29,7 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate
[Fact]
public async Task ServerSupportsAuthButDisabled_Error()
{
- using var host = await CreateHostAsync(supportsAuth: true, isEnabled: false);
- var ex = Assert.Throws(() => host.Services.GetRequiredService>().Value);
+ var ex = await Assert.ThrowsAsync(async () => await CreateHostAsync(supportsAuth: true, isEnabled: false));
Assert.Equal("The Negotiate Authentication handler cannot be used on a server that directly supports Windows Authentication."
+ " Enable Windows Authentication for the server and the Negotiate Authentication handler will defer to it.", ex.Message);
}