Commit 7e28bf14 authored by Honfika's avatar Honfika

Use of Guid as a session identifer #427

parent a1d4de48
......@@ -164,8 +164,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
WriteToConsole("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
WriteToConsole(e.WebSession.Request.Url);
// create custom id for the request and store it in the UserData property
// It can be a simple integer, Guid, or any type
e.UserData = Guid.NewGuid();
// read request headers
requestHeaderHistory[e.Id] = e.WebSession.Request.Headers;
requestHeaderHistory[(Guid)e.UserData] = e.WebSession.Request.Headers;
////This sample shows how to get the multipart form data headers
//if (e.WebSession.Request.Host == "mail.yahoo.com" && e.WebSession.Request.IsMultipartFormData)
......@@ -221,7 +225,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
WriteToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
var ext = System.IO.Path.GetExtension(e.WebSession.Request.RequestUri.AbsolutePath);
string ext = System.IO.Path.GetExtension(e.WebSession.Request.RequestUri.AbsolutePath);
//if (ext == ".gif" || ext == ".png" || ext == ".jpg")
//{
......
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy.UnitTests
......@@ -10,7 +11,7 @@ namespace Titanium.Web.Proxy.UnitTests
[TestMethod]
public void Test_Acquire_Client_Token()
{
string token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
string token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", new InternalDataStore());
Assert.IsTrue(token.Length > 1);
}
}
......
......@@ -74,10 +74,14 @@ namespace Titanium.Web.Proxy.EventArguments
internal ProxyClient ProxyClient { get; }
/// <summary>
/// Returns a unique Id for this request/response session which is
/// same as the RequestId of WebSession.
/// Returns a user data for this request/response session which is
/// same as the user data of WebSession.
/// </summary>
public Guid Id => WebSession.RequestId;
public object UserData
{
get => WebSession.UserData;
set => WebSession.UserData = value;
}
/// <summary>
/// Does this session uses SSL?
......
......@@ -272,10 +272,12 @@ namespace Titanium.Web.Proxy
await connection.StreamWriter.WriteLineAsync("SM", cancellationToken);
await connection.StreamWriter.WriteLineAsync(cancellationToken);
await TcpHelper.SendHttp2(clientStream, connection.Stream, BufferSize,
#if NETCOREAPP2_1
await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc);
#endif
}
}
}
......
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Helpers;
......@@ -252,95 +254,5 @@ namespace Titanium.Web.Proxy.Helpers
cancellationTokenSource,
exceptionFunc);
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers
/// as prefix
/// Usefull for websocket requests
/// Task-based Asynchronous Pattern
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="connectionId"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendHttp2(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource, Guid connectionId,
ExceptionHandler exceptionFunc)
{
// Now async relay all server=>client & client=>server data
var sendRelay =
CopyHttp2FrameAsync(clientStream, serverStream, onDataSend, bufferSize, connectionId,
cancellationTokenSource.Token);
var receiveRelay =
CopyHttp2FrameAsync(serverStream, clientStream, onDataReceive, bufferSize, connectionId,
cancellationTokenSource.Token);
await Task.WhenAny(sendRelay, receiveRelay);
cancellationTokenSource.Cancel();
await Task.WhenAll(sendRelay, receiveRelay);
}
private static async Task CopyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy,
int bufferSize, Guid connectionId, CancellationToken cancellationToken)
{
var headerBuffer = new byte[9];
var buffer = new byte[32768];
while (true)
{
int read = await ForceRead(input, headerBuffer, 0, 9, cancellationToken);
if (read != 9)
{
return;
}
int length = (headerBuffer[0] << 16) + (headerBuffer[1] << 8) + headerBuffer[2];
byte type = headerBuffer[3];
byte flags = headerBuffer[4];
int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) +
headerBuffer[8];
read = await ForceRead(input, buffer, 0, length, cancellationToken);
if (read != length)
{
return;
}
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length, cancellationToken);
await output.WriteAsync(buffer, 0, length, cancellationToken);
/*using (var fs = new System.IO.FileStream($@"c:\11\{connectionId}.{streamId}.dat", FileMode.Append))
{
fs.Write(headerBuffer, 0, headerBuffer.Length);
fs.Write(buffer, 0, length);
}*/
}
}
private static async Task<int> ForceRead(Stream input, byte[] buffer, int offset, int bytesToRead,
CancellationToken cancellationToken)
{
int totalRead = 0;
while (bytesToRead > 0)
{
int read = await input.ReadAsync(buffer, offset, bytesToRead, cancellationToken);
if (read == -1)
{
break;
}
totalRead += read;
bytesToRead -= read;
offset += read;
}
return totalRead;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
......@@ -20,7 +21,6 @@ namespace Titanium.Web.Proxy.Http
{
this.bufferSize = bufferSize;
RequestId = Guid.NewGuid();
Request = request ?? new Request();
Response = response ?? new Response();
}
......@@ -31,9 +31,14 @@ namespace Titanium.Web.Proxy.Http
internal TcpServerConnection ServerConnection { get; set; }
/// <summary>
/// Request ID.
/// Stores internal data for the session.
/// </summary>
public Guid RequestId { get; }
internal InternalDataStore Data { get; } = new InternalDataStore();
/// <summary>
/// Gets or sets the user data.
/// </summary>
public object UserData { get; set; }
/// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made
......
using System.Collections.Generic;
namespace Titanium.Web.Proxy.Http
{
class InternalDataStore : Dictionary<string, object>
{
public bool TryGetValueAs<T>(string key, out T value)
{
bool result = TryGetValue(key, out var value1);
if (result)
{
value = (T)value1;
}
else
{
value = default;
}
return result;
}
public T GetAs<T>(string key)
{
return (T)this[key];
}
}
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
......@@ -14,19 +15,16 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
internal class WinAuthEndPoint
{
/// <summary>
/// Keep track of auth states for reuse in final challenge response
/// </summary>
private static readonly IDictionary<Guid, State> authStates = new ConcurrentDictionary<Guid, State>();
private const string authStateKey = "AuthState";
/// <summary>
/// Acquire the intial client token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <param name="data"></param>
/// <returns></returns>
internal static byte[] AcquireInitialSecurityToken(string hostname, string authScheme, Guid requestId)
internal static byte[] AcquireInitialSecurityToken(string hostname, string authScheme, InternalDataStore data)
{
byte[] token;
......@@ -75,7 +73,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
state.AuthState = State.WinAuthState.INITIAL_TOKEN;
token = clientToken.GetBytes();
authStates.Add(requestId, state);
data.Add(authStateKey, state);
}
finally
{
......@@ -91,9 +89,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary>
/// <param name="hostname"></param>
/// <param name="serverChallenge"></param>
/// <param name="requestId"></param>
/// <param name="data"></param>
/// <returns></returns>
internal static byte[] AcquireFinalSecurityToken(string hostname, byte[] serverChallenge, Guid requestId)
internal static byte[] AcquireFinalSecurityToken(string hostname, byte[] serverChallenge, InternalDataStore data)
{
byte[] token;
......@@ -104,7 +102,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
try
{
var state = authStates[requestId];
var state = data.GetAs<State>(authStateKey);
state.UpdatePresence();
......@@ -138,35 +136,16 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return token;
}
/// <summary>
/// Clear any hanging states
/// </summary>
/// <param name="stateCacheTimeOutMinutes"></param>
internal static async void ClearIdleStates(int stateCacheTimeOutMinutes)
{
var cutOff = DateTime.Now.AddMinutes(-1 * stateCacheTimeOutMinutes);
var outdated = authStates.Where(x => x.Value.LastSeen < cutOff).ToList();
foreach (var cache in outdated)
{
authStates.Remove(cache.Key);
}
// after a minute come back to check for outdated certificates in cache
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="data"></param>
/// <param name="expectedAuthState"></param>
/// <returns></returns>
internal static bool ValidateWinAuthState(Guid requestId, State.WinAuthState expectedAuthState)
internal static bool ValidateWinAuthState(InternalDataStore data, State.WinAuthState expectedAuthState)
{
bool stateExists = authStates.TryGetValue(requestId, out var state);
bool stateExists = data.TryGetValueAs(authStateKey, out State state);
if (expectedAuthState == State.WinAuthState.UNAUTHORIZED)
{
......@@ -188,10 +167,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// <summary>
/// Set the AuthState to authorized and update the connection state lifetime
/// </summary>
/// <param name="requestId"></param>
internal static void AuthenticatedResponse(Guid requestId)
/// <param name="data"></param>
internal static void AuthenticatedResponse(InternalDataStore data)
{
if (authStates.TryGetValue(requestId, out var state))
if (data.TryGetValueAs(authStateKey, out State state))
{
state.AuthState = State.WinAuthState.AUTHORIZED;
state.UpdatePresence();
......
using System;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy.Network.WinAuth
......@@ -16,11 +17,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <param name="data"></param>
/// <returns></returns>
internal static string GetInitialAuthToken(string serverHostname, string authScheme, Guid requestId)
internal static string GetInitialAuthToken(string serverHostname, string authScheme, InternalDataStore data)
{
var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId);
var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, data);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
......@@ -29,13 +30,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="serverToken"></param>
/// <param name="requestId"></param>
/// <param name="data"></param>
/// <returns></returns>
internal static string GetFinalAuthToken(string serverHostname, string serverToken, Guid requestId)
internal static string GetFinalAuthToken(string serverHostname, string serverToken, InternalDataStore data)
{
var tokenBytes =
WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken),
requestId);
data);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
......
......@@ -511,11 +511,6 @@ namespace Titanium.Web.Proxy
CertificateManager.ClearIdleCertificates();
if (RunTime.IsWindows && !RunTime.IsRunningOnMono)
{
WinAuthEndPoint.ClearIdleStates(2);
}
foreach (var endPoint in ProxyEndPoints)
{
Listen(endPoint);
......
......@@ -39,7 +39,7 @@ namespace Titanium.Web.Proxy
}
else
{
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.RequestId);
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data);
}
}
......
......@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy
var expectedAuthState =
scheme == null ? State.WinAuthState.INITIAL_TOKEN : State.WinAuthState.UNAUTHORIZED;
if (!WinAuthEndPoint.ValidateWinAuthState(args.WebSession.RequestId, expectedAuthState))
if (!WinAuthEndPoint.ValidateWinAuthState(args.WebSession.Data, expectedAuthState))
{
// Invalid state, create proper error message to client
await RewriteUnauthorizedResponse(args);
......@@ -111,7 +111,7 @@ namespace Titanium.Web.Proxy
// initial value will match exactly any of the schemes
if (scheme != null)
{
string clientToken = WinAuthHandler.GetInitialAuthToken(request.Host, scheme, args.Id);
string clientToken = WinAuthHandler.GetInitialAuthToken(request.Host, scheme, args.WebSession.Data);
string auth = string.Concat(scheme, clientToken);
......@@ -133,7 +133,7 @@ namespace Titanium.Web.Proxy
authHeader.Value.Length > x.Length + 1);
string serverToken = authHeader.Value.Substring(scheme.Length + 1);
string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.Id);
string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.WebSession.Data);
string auth = string.Concat(scheme, clientToken);
......
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