Commit 6b0d9305 authored by Steve Syfuhs's avatar Steve Syfuhs

Added ability to inject a custom authentication mechanism for things like NTLM...

Added ability to inject a custom authentication mechanism for things like NTLM or Kerberos, instead of falling back to Basic.
parent 79cdc5e9
namespace Titanium.Web.Proxy.Models
{
public enum ProxyAuthenticationResult
{
/// <summary>
/// Indicates the authentication request was successful
/// </summary>
Success,
/// <summary>
/// Indicates the authentication request failed
/// </summary>
Failure,
/// <summary>
/// Indicates that this stage of the authentication request succeeded
/// And a second pass of the handshake needs to occur
/// </summary>
ContinuationNeeded
}
/// <summary>
/// A context container for authentication flows
/// </summary>
public class ProxyAuthenticationContext
{
/// <summary>
/// The result of the current authentication request
/// </summary>
public ProxyAuthenticationResult Result { get; set; }
/// <summary>
/// An optional continuation token to return to the caller if set
/// </summary>
public string Continuation { get; set; }
public static ProxyAuthenticationContext Failed()
{
return new ProxyAuthenticationContext
{
Result = ProxyAuthenticationResult.Failure,
Continuation = null
};
}
public static ProxyAuthenticationContext Succeeded()
{
return new ProxyAuthenticationContext
{
Result = ProxyAuthenticationResult.Success,
Continuation = null
};
}
}
}
......@@ -21,7 +21,7 @@ namespace Titanium.Web.Proxy
private async Task<bool> checkAuthorization(SessionEventArgsBase session)
{
// If we are not authorizing clients return true
if (AuthenticateUserFunc == null)
if (AuthenticateUserFunc == null && AuthenticateSchemeFunc == null)
{
return true;
}
......@@ -38,8 +38,49 @@ namespace Titanium.Web.Proxy
}
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (headerValueParts.Length != 2 ||
!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
if (headerValueParts.Length != 2)
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
if (AuthenticateUserFunc != null)
{
return await authenticateUserBasic(session, headerValueParts);
}
if (AuthenticateSchemeFunc != null)
{
var result = await AuthenticateSchemeFunc(session, headerValueParts[0], headerValueParts[1]);
if (result.Result == ProxyAuthenticationResult.ContinuationNeeded)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid", result.Continuation);
return false;
}
return result.Result == ProxyAuthenticationResult.Success;
}
return false;
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e,
httpHeaders));
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
}
private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, string[] headerValueParts)
{
if (!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
......@@ -65,23 +106,13 @@ namespace Titanium.Web.Proxy
return authenticated;
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e,
httpHeaders));
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
}
/// <summary>
/// Create an authentication required response.
/// </summary>
/// <param name="description">Response description.</param>
/// <returns></returns>
private Response createAuthentication407Response(string description)
private Response createAuthentication407Response(string description, string continuation = null)
{
var response = new Response
{
......@@ -90,11 +121,39 @@ namespace Titanium.Web.Proxy
StatusDescription = description
};
if (!string.IsNullOrWhiteSpace(continuation))
{
return createContinuationResponse(response, continuation);
}
if (AuthenticateUserFunc != null)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyRealm}\"");
}
if (AuthenticateSchemeFunc != null)
{
foreach (var scheme in SupportedAuthenticationSchemes)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, scheme);
}
}
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
response.Headers.FixProxyHeaders();
return response;
}
private Response createContinuationResponse(Response response, string continuation)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, continuation);
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ConnectionKeepAlive);
response.Headers.FixProxyHeaders();
return response;
}
}
}
......@@ -270,6 +270,18 @@ namespace Titanium.Web.Proxy
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// A pluggable callback to authenticate clients by scheme instead of requiring basic auth.
/// Parameters are current working session, schemeType, and token as provided by a calling client.
/// Should return success for successful authentication, continuation if the package requests, or failure.
/// </summary>
public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> AuthenticateSchemeFunc { get; set; }
/// <summary>
/// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if authentication is required.
/// </summary>
public IEnumerable<string> SupportedAuthenticationSchemes { get; set; } = new string[0];
/// <summary>
/// Dispose the Proxy instance.
/// </summary>
......
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