// Copyright (c) Microsoft Open Technologies, Inc. // All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING // WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF // TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR // NON-INFRINGEMENT. // See the Apache 2 License for the specific language governing // permissions and limitations under the License. // ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // ----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Authentication.ExtendedProtection; using System.Threading.Tasks; using Microsoft.AspNet.Server.WebListener; using Xunit; using Xunit.Extensions; namespace Microsoft.AspNet.Security.Windows.Tests { using AppFunc = Func, Task>; public class NegotiateTests { private const string Address = "http://localhost:8080/"; private const string SecureAddress = "https://localhost:9090/"; private const int DefaultStatusCode = 201; [Theory] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_PartialMatch_PassedThrough(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(SimpleApp); IDictionary emptyEnv = CreateEmptyRequest("Authorization", package + "ion blablabla"); await windowsAuth.Invoke(emptyEnv); Assert.Equal(DefaultStatusCode, emptyEnv.Get("owin.ResponseStatusCode")); var responseHeaders = emptyEnv.Get>("owin.ResponseHeaders"); Assert.Equal(0, responseHeaders.Count); } [Theory] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_BadData_400(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(SimpleApp); IDictionary emptyEnv = CreateEmptyRequest("Authorization", package + " blablabla"); await windowsAuth.Invoke(emptyEnv); Assert.Equal(400, emptyEnv.Get("owin.ResponseStatusCode")); var responseHeaders = emptyEnv.Get>("owin.ResponseHeaders"); Assert.Equal(0, responseHeaders.Count); } [Theory] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_AppSets401_401WithChallenge(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(SimpleApp401); windowsAuth.AuthenticationSchemes = (AuthTypes)Enum.Parse(typeof(AuthTypes), package, true); IDictionary emptyEnv = CreateEmptyRequest(); await windowsAuth.Invoke(emptyEnv); FireOnSendingHeadersActions(emptyEnv); Assert.Equal(401, emptyEnv.Get("owin.ResponseStatusCode")); var responseHeaders = emptyEnv.Get>("owin.ResponseHeaders"); Assert.Equal(1, responseHeaders.Count); Assert.NotNull(responseHeaders.Get("www-authenticate")); Assert.Equal(package, responseHeaders.Get("www-authenticate")); } [Theory(Skip = "Broken")] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_ClientAuthenticates_Success(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(new DenyAnonymous(SimpleApp).Invoke); windowsAuth.AuthenticationSchemes = (AuthTypes)Enum.Parse(typeof(AuthTypes), package, true); using (CreateServer(windowsAuth.Invoke)) { HttpResponseMessage response = await SendAuthRequestAsync(Address); Assert.Equal(DefaultStatusCode, (int)response.StatusCode); } } [Theory(Skip = "Broken")] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_ClientAuthenticatesMultipleTimes_Success(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(new DenyAnonymous(SimpleApp).Invoke); windowsAuth.AuthenticationSchemes = (AuthTypes)Enum.Parse(typeof(AuthTypes), package, true); using (CreateServer(windowsAuth.Invoke)) { for (int i = 0; i < 10; i++) { HttpResponseMessage response = await SendAuthRequestAsync(Address); Assert.Equal(DefaultStatusCode, (int)response.StatusCode); } } } [Theory] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_AnonmousClient_401(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(new DenyAnonymous(SimpleApp).Invoke); windowsAuth.AuthenticationSchemes = (AuthTypes)Enum.Parse(typeof(AuthTypes), package, true); using (CreateServer(windowsAuth.Invoke)) { HttpResponseMessage response = await SendRequestAsync(Address); Assert.Equal(401, (int)response.StatusCode); Assert.Equal(package, response.Headers.WwwAuthenticate.ToString()); } } [Fact(Skip = "Broken")] public async Task UnsafeSharedNTLM_AuthenticatedClient_Success() { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(new DenyAnonymous(SimpleApp).Invoke); windowsAuth.AuthenticationSchemes = AuthTypes.Ntlm; windowsAuth.UnsafeConnectionNtlmAuthentication = true; using (CreateServer(windowsAuth.Invoke)) { WebRequestHandler handler = new WebRequestHandler(); CredentialCache cache = new CredentialCache(); cache.Add(new Uri(Address), "NTLM", CredentialCache.DefaultNetworkCredentials); handler.Credentials = cache; handler.UnsafeAuthenticatedConnectionSharing = true; using (HttpClient client = new HttpClient(handler)) { HttpResponseMessage response = await client.GetAsync(Address); Assert.Equal(DefaultStatusCode, (int)response.StatusCode); response.EnsureSuccessStatusCode(); // Remove the credentials before try two just to prove they aren't used. cache.Remove(new Uri(Address), "NTLM"); response = await client.GetAsync(Address); Assert.Equal(DefaultStatusCode, (int)response.StatusCode); } } } [Theory(Skip = "Broken")] [InlineData("Negotiate")] [InlineData("NTLM")] public async Task Negotiate_ClientAuthenticatesWithCbt_Success(string package) { WindowsAuthMiddleware windowsAuth = new WindowsAuthMiddleware(new DenyAnonymous(SimpleApp).Invoke); windowsAuth.AuthenticationSchemes = (AuthTypes)Enum.Parse(typeof(AuthTypes), package, true); windowsAuth.ExtendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Always); using (CreateSecureServer(windowsAuth.Invoke)) { HttpResponseMessage response = await SendAuthRequestAsync(SecureAddress); Assert.Equal(DefaultStatusCode, (int)response.StatusCode); } } private IDictionary CreateEmptyRequest(string header = null, string value = null, string connectionId = "Random") { IDictionary env = new Dictionary(); var requestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); env["owin.RequestHeaders"] = requestHeaders; if (header != null) { requestHeaders[header] = new string[] { value }; } env["owin.ResponseHeaders"] = new Dictionary(StringComparer.OrdinalIgnoreCase); var onSendingHeadersActions = new List, object>>(); env["server.OnSendingHeaders"] = new Action, object>( (a, b) => onSendingHeadersActions.Add(new Tuple, object>(a, b))); env["test.OnSendingHeadersActions"] = onSendingHeadersActions; env["server.ConnectionId"] = connectionId; return env; } private void FireOnSendingHeadersActions(IDictionary env) { var onSendingHeadersActions = env.Get, object>>>("test.OnSendingHeadersActions"); foreach (var actionPair in onSendingHeadersActions.Reverse()) { actionPair.Item1(actionPair.Item2); } } private IDisposable CreateServer(AppFunc app) { IDictionary properties = new Dictionary(); IList> addresses = new List>(); properties["host.Addresses"] = addresses; IDictionary address = new Dictionary(); addresses.Add(address); address["scheme"] = "http"; address["host"] = "localhost"; address["port"] = "8080"; address["path"] = string.Empty; return OwinServerFactory.Create(app, properties); } private IDisposable CreateSecureServer(AppFunc app) { IDictionary properties = new Dictionary(); IList> addresses = new List>(); properties["host.Addresses"] = addresses; IDictionary address = new Dictionary(); addresses.Add(address); address["scheme"] = "https"; address["host"] = "localhost"; address["port"] = "9090"; address["path"] = string.Empty; return OwinServerFactory.Create(app, properties); } private async Task SendRequestAsync(string uri) { using (HttpClient client = new HttpClient()) { return await client.GetAsync(uri); } } private async Task SendAuthRequestAsync(string uri) { WebRequestHandler handler = new WebRequestHandler(); handler.UseDefaultCredentials = true; handler.ServerCertificateValidationCallback = (a, b, c, d) => true; using (HttpClient client = new HttpClient(handler)) { return await client.GetAsync(uri); } } private Task SimpleApp(IDictionary env) { env["owin.ResponseStatusCode"] = DefaultStatusCode; return Task.FromResult(null); } private Task SimpleApp401(IDictionary env) { env["owin.ResponseStatusCode"] = 401; return Task.FromResult(null); } } }