Commit e041a288 authored by Björn Weström's avatar Björn Weström

State handling of WinAuth, fixes issue with failed authentication

parent b0379905
......@@ -7,12 +7,24 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary>
internal class State
{
/// <summary>
/// States during Windows Authentication
/// </summary>
public enum WinAuthState
{
UNAUTHORIZED,
INITIAL_TOKEN,
FINAL_TOKEN,
AUTHORIZED
};
internal State()
{
Credentials = new Common.SecurityHandle(0);
Context = new Common.SecurityHandle(0);
LastSeen = DateTime.Now;
AuthState = WinAuthState.UNAUTHORIZED;
}
/// <summary>
......@@ -30,10 +42,16 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary>
internal DateTime LastSeen;
/// <summary>
/// Current state of the authentication process
/// </summary>
internal WinAuthState AuthState;
internal void ResetHandles()
{
Credentials.Reset();
Context.Reset();
AuthState = WinAuthState.UNAUTHORIZED;
}
internal void UpdatePresence()
......
......@@ -79,6 +79,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return null;
}
state.AuthState = State.WinAuthState.INITIAL_TOKEN;
token = clientToken.GetBytes();
authStates.Add(requestId, state);
}
......@@ -135,7 +136,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return null;
}
authStates.Remove(requestId);
state.AuthState = State.WinAuthState.FINAL_TOKEN;
token = clientToken.GetBytes();
}
finally
......@@ -166,6 +167,51 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
await Task.Delay(1000 * 60);
}
/// <summary>
/// Validates that the current WinAuth state of the connection matches the
/// expectation, used to detect failed authentication
/// </summary>
/// <param name="requestId"></param>
/// <param name="expectedAuthState"></param>
/// <returns></returns>
internal static bool ValidateWinAuthState(Guid requestId, State.WinAuthState expectedAuthState)
{
State state;
var stateExists = authStates.TryGetValue(requestId, out state);
if (expectedAuthState == State.WinAuthState.UNAUTHORIZED)
{
// Validation before initial token
return (stateExists == false ||
state.AuthState == State.WinAuthState.UNAUTHORIZED ||
state.AuthState == State.WinAuthState.AUTHORIZED); // Server may require re-authentication on an open connection
}
if (expectedAuthState == State.WinAuthState.INITIAL_TOKEN)
{
// Validation before final token
return (stateExists &&
(state.AuthState == State.WinAuthState.INITIAL_TOKEN ||
state.AuthState == State.WinAuthState.AUTHORIZED)); // Server may require re-authentication on an open connection
}
throw new Exception("Unsupported validation of WinAuthState");
}
/// <summary>
/// Set the AuthState to authorized and update the connection state lifetime
/// </summary>
/// <param name="requestId"></param>
internal static void AuthenticatedResponse(Guid requestId)
{
State state;
if (authStates.TryGetValue(requestId, out state))
{
state.AuthState = State.WinAuthState.AUTHORIZED;
state.UpdatePresence();
}
}
#region Native calls to secur32.dll
[DllImport("secur32.dll", SetLastError = true)]
......
......@@ -7,6 +7,7 @@ using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy
{
......@@ -31,9 +32,16 @@ namespace Titanium.Web.Proxy
args.ReRequest = false;
//check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized)
if (isWindowsAuthenticationEnabledAndSupported)
{
await Handle401UnAuthorized(args);
if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
await Handle401UnAuthorized(args);
}
else
{
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.RequestId);
}
}
response.OriginalHasBody = response.HasBody;
......
......@@ -6,6 +6,7 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy
{
......@@ -84,6 +85,14 @@ namespace Titanium.Web.Proxy
{
string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
if ((scheme != null && !WinAuthEndPoint.ValidateWinAuthState(args.WebSession.RequestId, State.WinAuthState.UNAUTHORIZED)) ||
(scheme == null && !WinAuthEndPoint.ValidateWinAuthState(args.WebSession.RequestId, State.WinAuthState.INITIAL_TOKEN)))
{
// Invalid state, create proper error message to client
await RewriteUnauthorizedResponse(args);
return;
}
var request = args.WebSession.Request;
//clear any existing headers to avoid confusing bad servers
......@@ -134,5 +143,44 @@ namespace Titanium.Web.Proxy
args.ReRequest = true;
}
}
/// <summary>
/// Rewrites the response body for failed authentication
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
internal async Task RewriteUnauthorizedResponse(SessionEventArgs args)
{
var response = args.WebSession.Response;
// Strip authentication headers to avoid credentials prompt in client web browser
foreach (var authHeaderName in authHeaderNames)
{
response.Headers.RemoveHeader(authHeaderName);
}
// Add custom div to body to clarify that the proxy (not the client browser) failed authentication
string authErrorMessage = "<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
args.ProxyClient.TcpClient.Client.LocalEndPoint +
") failed. Please check credentials.</h2></div>";
string originalErrorMessage = "<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
string body = await args.GetResponseBodyAsString();
if (body.ToLower().Contains("<body>"))
{
var bodyPos = body.ToLower().IndexOf("<body>") + ("<body>").Length;
body = body.Insert(bodyPos, authErrorMessage + originalErrorMessage);
}
else
{
// Cannot parse response body, replace it
body = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
"<body>" +
authErrorMessage +
"</body>" +
"</html>";
}
args.SetResponseBodyString(body);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment