From a06bd83f2d1b1e9edc190fa09bdb7f0752783a7f Mon Sep 17 00:00:00 2001 From: sr55 Date: Sat, 11 Apr 2020 13:00:41 +0100 Subject: WinGui: Improvements to the Process Isolation Worker. - Harden the worker process. Token is now required as a HTTP header for all actions. - Added an option to portable.ini to completely disable this functioanlity. May be useful for some enterprise environents - Few fixes --- win/CS/HandBrake.Worker/HttpServer.cs | 46 +++++++++++++++--- win/CS/HandBrake.Worker/Program.cs | 8 +++- .../HandBrakeWPF/Converters/OptionTabConverter.cs | 5 +- win/CS/HandBrakeWPF/Instance/RemoteInstance.cs | 20 +++++--- win/CS/HandBrakeWPF/Utilities/HttpRequestBase.cs | 54 ++++++++++++++++------ win/CS/HandBrakeWPF/Utilities/Portable.cs | 16 +++++++ win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs | 8 ++++ win/CS/HandBrakeWPF/Views/OptionsView.xaml | 22 +++++---- win/CS/HandBrakeWPF/portable.ini.template | 6 ++- 9 files changed, 146 insertions(+), 39 deletions(-) (limited to 'win') diff --git a/win/CS/HandBrake.Worker/HttpServer.cs b/win/CS/HandBrake.Worker/HttpServer.cs index 3c9a36b37..ef8811ab6 100644 --- a/win/CS/HandBrake.Worker/HttpServer.cs +++ b/win/CS/HandBrake.Worker/HttpServer.cs @@ -19,16 +19,20 @@ namespace HandBrake.Worker public class HttpServer { + private readonly string uiToken; + private readonly HttpListener httpListener = new HttpListener(); private readonly Dictionary> apiHandlers; - public HttpServer(Dictionary> apiCalls, int port) + public HttpServer(Dictionary> apiCalls, int port, string token) { if (!HttpListener.IsSupported) { throw new NotSupportedException("HttpListener not supported on this computer."); } + this.uiToken = token; + // Store the Handlers this.apiHandlers = new Dictionary>(apiCalls); @@ -65,15 +69,30 @@ namespace HandBrake.Worker try { string path = context.Request.RawUrl.TrimStart('/').TrimEnd('/'); + string token = context.Request.Headers.Get("token"); + if (!this.IsAuthenticated(token)) + { + string rstr = "Worker: Access Denied. The token provided in the HTTP header was not valid."; + Console.WriteLine(rstr); + byte[] buf = Encoding.UTF8.GetBytes(rstr); + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + context.Response.ContentLength64 = buf.Length; + context.Response.OutputStream.Write(buf, 0, buf.Length); + return; + } + Debug.WriteLine("Handling call to: " + path); if (this.apiHandlers.TryGetValue(path, out var actionToPerform)) { string rstr = actionToPerform(context.Request); - byte[] buf = Encoding.UTF8.GetBytes(rstr); - context.Response.ContentLength64 = buf.Length; - context.Response.OutputStream.Write(buf, 0, buf.Length); + if (!string.IsNullOrEmpty(rstr)) + { + byte[] buf = Encoding.UTF8.GetBytes(rstr); + context.Response.ContentLength64 = buf.Length; + context.Response.OutputStream.Write(buf, 0, buf.Length); + } } else { @@ -85,7 +104,7 @@ namespace HandBrake.Worker } catch (Exception exc) { - Debug.WriteLine(exc); + Console.WriteLine("Worker: Listener Thread: " + exc); } finally { @@ -97,7 +116,7 @@ namespace HandBrake.Worker } catch (Exception exc) { - Debug.WriteLine(exc); + Console.WriteLine("Worker: " + exc); } }); } @@ -107,5 +126,20 @@ namespace HandBrake.Worker this.httpListener.Stop(); this.httpListener.Close(); } + + public bool IsAuthenticated(string token) + { + if (string.IsNullOrEmpty(token)) + { + return false; + } + + if (token != this.uiToken) + { + return false; + } + + return true; + } } } \ No newline at end of file diff --git a/win/CS/HandBrake.Worker/Program.cs b/win/CS/HandBrake.Worker/Program.cs index f925ade38..a71192a41 100644 --- a/win/CS/HandBrake.Worker/Program.cs +++ b/win/CS/HandBrake.Worker/Program.cs @@ -25,6 +25,7 @@ namespace HandBrake.Worker public static void Main(string[] args) { int port = 8037; // Default Port; + string token = null; if (args.Length != 0) { @@ -38,6 +39,11 @@ namespace HandBrake.Worker port = parsedPort; } } + + if (argument.StartsWith("--token")) + { + token = argument.TrimStart("--token=".ToCharArray()); + } } } @@ -47,7 +53,7 @@ namespace HandBrake.Worker Console.WriteLine("Worker: Starting Web Server on port {0} ...", port); Dictionary> apiHandlers = RegisterApiHandlers(); - HttpServer webServer = new HttpServer(apiHandlers, port); + HttpServer webServer = new HttpServer(apiHandlers, port, token); webServer.Run(); Console.WriteLine("Worker: Server Started"); diff --git a/win/CS/HandBrakeWPF/Converters/OptionTabConverter.cs b/win/CS/HandBrakeWPF/Converters/OptionTabConverter.cs index 9ad7111ec..069918a71 100644 --- a/win/CS/HandBrakeWPF/Converters/OptionTabConverter.cs +++ b/win/CS/HandBrakeWPF/Converters/OptionTabConverter.cs @@ -22,11 +22,14 @@ namespace HandBrakeWPF.Converters public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { OptionsTab[] tabs = value as OptionsTab[]; - if (tabs != null && UwpDetect.IsUWP()) + if (tabs != null && (UwpDetect.IsUWP() || !Portable.IsUpdateCheckEnabled())) { return tabs.Where(s => s != OptionsTab.Updates).ToArray(); } + + + return value; } diff --git a/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs b/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs index 87af203c8..de0d50cc4 100644 --- a/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs +++ b/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs @@ -17,6 +17,7 @@ namespace HandBrakeWPF.Instance using System.Linq; using System.Net; using System.Net.NetworkInformation; + using System.Text; using System.Threading.Tasks; using System.Timers; using System.Windows.Media.Animation; @@ -48,9 +49,7 @@ namespace HandBrakeWPF.Instance public class RemoteInstance : HttpRequestBase, IEncodeInstance, IDisposable { private readonly HBConfiguration configuration; - private readonly ILog logService; - private readonly IUserSettingService userSettingService; private const double EncodePollIntervalMs = 500; @@ -94,7 +93,7 @@ namespace HandBrakeWPF.Instance EnableHardwareAcceleration = true, LogDirectory = DirectoryUtilities.GetLogDirectory(), LogVerbosity = this.userSettingService.GetUserSetting(UserSettingConstants.Verbosity) - }; + }; initCommand.LogFile = Path.Combine(initCommand.LogDirectory, string.Format("activity_log.worker.{0}.txt", GeneralUtilities.ProcessId)); @@ -134,7 +133,6 @@ namespace HandBrakeWPF.Instance public void Dispose() { - this.client?.Dispose(); this.workerProcess?.Dispose(); } @@ -142,12 +140,15 @@ namespace HandBrakeWPF.Instance { if (this.workerProcess == null || this.workerProcess.HasExited) { + var plainTextBytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()); + this.base64Token = Convert.ToBase64String(plainTextBytes); + workerProcess = new Process { StartInfo = { FileName = "HandBrake.Worker.exe", - Arguments = string.Format(" --port={0}", port), + Arguments = string.Format(" --port={0} --token={1}", port, this.base64Token), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, @@ -227,8 +228,6 @@ namespace HandBrakeWPF.Instance } response = await this.MakeHttpGetRequest("PollEncodeProgress"); - - this.retryCount = 0; // Reset } catch (Exception e) { @@ -241,6 +240,8 @@ namespace HandBrakeWPF.Instance return; } + this.retryCount = 0; // Reset + string statusJson = response.JsonResponse; JsonState state = JsonConvert.DeserializeObject(statusJson); @@ -278,6 +279,11 @@ namespace HandBrakeWPF.Instance private int GetOpenPort(int startPort) { + if (startPort == 0) + { + startPort = 8037; + } + int portStartIndex = startPort; IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); diff --git a/win/CS/HandBrakeWPF/Utilities/HttpRequestBase.cs b/win/CS/HandBrakeWPF/Utilities/HttpRequestBase.cs index cc2898b43..fd23565e3 100644 --- a/win/CS/HandBrakeWPF/Utilities/HttpRequestBase.cs +++ b/win/CS/HandBrakeWPF/Utilities/HttpRequestBase.cs @@ -10,6 +10,7 @@ namespace HandBrakeWPF.Utilities { using System; + using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -20,11 +21,14 @@ namespace HandBrakeWPF.Utilities public class HttpRequestBase { - protected readonly JsonSerializerSettings jsonNetSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; - protected HttpClient client = new HttpClient(); protected string serverUrl; + protected int port; + protected string base64Token; + + private readonly JsonSerializerSettings jsonNetSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + public async Task MakeHttpJsonPostRequest(string urlPath, string json) { if (string.IsNullOrEmpty(json)) @@ -32,14 +36,26 @@ namespace HandBrakeWPF.Utilities throw new InvalidOperationException("No Post Values Found."); } - StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); - HttpResponseMessage response = await client.PostAsync(this.serverUrl + urlPath, content); - if (response != null) + using (HttpClient client = new HttpClient()) { - string returnContent = await response.Content.ReadAsStringAsync(); - ServerResponse serverResponse = new ServerResponse(response.IsSuccessStatusCode, returnContent); + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, this.serverUrl + urlPath); + if (!string.IsNullOrEmpty(this.base64Token)) + { + requestMessage.Headers.Add("token", this.base64Token); + } + + requestMessage.Content = new StringContent(json, Encoding.UTF8, "application/json"); - return serverResponse; + using (HttpResponseMessage response = await client.SendAsync(requestMessage)) + { + if (response != null) + { + string returnContent = await response.Content.ReadAsStringAsync(); + ServerResponse serverResponse = new ServerResponse(response.IsSuccessStatusCode, returnContent); + + return serverResponse; + } + } } return null; @@ -47,13 +63,25 @@ namespace HandBrakeWPF.Utilities public async Task MakeHttpGetRequest(string urlPath) { - HttpResponseMessage response = await client.GetAsync(this.serverUrl + urlPath); - if (response != null) + using (HttpClient client = new HttpClient()) { - string returnContent = await response.Content.ReadAsStringAsync(); - ServerResponse serverResponse = new ServerResponse(response.IsSuccessStatusCode, returnContent); + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, this.serverUrl + urlPath); + if (!string.IsNullOrEmpty(this.base64Token)) + { + requestMessage.Headers.Add("token", this.base64Token); + } + + using (HttpResponseMessage response = await client.SendAsync(requestMessage)) + { + if (response != null) + { + string returnContent = await response.Content.ReadAsStringAsync(); + ServerResponse serverResponse = null; + serverResponse = response.StatusCode == HttpStatusCode.Unauthorized ? new ServerResponse(false, returnContent) : new ServerResponse(response.IsSuccessStatusCode, returnContent); - return serverResponse; + return serverResponse; + } + } } return null; diff --git a/win/CS/HandBrakeWPF/Utilities/Portable.cs b/win/CS/HandBrakeWPF/Utilities/Portable.cs index ecde42d82..578c3fe76 100644 --- a/win/CS/HandBrakeWPF/Utilities/Portable.cs +++ b/win/CS/HandBrakeWPF/Utilities/Portable.cs @@ -193,6 +193,22 @@ namespace HandBrakeWPF.Utilities return true; // Default to On. } + public static bool IsRemoteWorkerProcessEnabled() + { + if (keyPairs.ContainsKey("remote.worker.enabled")) + { + string enabled = keyPairs["remote.worker.enabled"]; + if (!string.IsNullOrEmpty(enabled) && enabled.Trim() == "true") + { + return true; + } + + return false; + } + + return true; // Default to On. + } + /// /// The get temp directory. /// diff --git a/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs index 6796141fd..0e92023dd 100644 --- a/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs @@ -1354,6 +1354,14 @@ namespace HandBrakeWPF.ViewModels } } + public bool IsRemoteWorkedAllowed + { + get + { + return Portable.IsRemoteWorkerProcessEnabled(); + } + } + #region Public Methods /// diff --git a/win/CS/HandBrakeWPF/Views/OptionsView.xaml b/win/CS/HandBrakeWPF/Views/OptionsView.xaml index 0abb41364..606b940ac 100644 --- a/win/CS/HandBrakeWPF/Views/OptionsView.xaml +++ b/win/CS/HandBrakeWPF/Views/OptionsView.xaml @@ -113,7 +113,7 @@ - + @@ -185,7 +185,7 @@