diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index 746fecaf..da999108 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -1,1883 +1,1883 @@ -/* - Copyright NetFoundry Inc. - - 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Input; -using System.IO; -using System.ServiceProcess; -using System.Linq; -using System.Diagnostics; -using System.Windows.Controls; -using System.Drawing; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Media.Animation; -using System.Web; -using Microsoft.Toolkit.Uwp.Notifications; - -using ZitiDesktopEdge.Models; -using ZitiDesktopEdge.DataStructures; -using ZitiDesktopEdge.ServiceClient; -using ZitiDesktopEdge.Utility; - -using NLog; -using NLog.Config; -using NLog.Targets; -using Microsoft.Win32; - -using Ziti.Desktop.Edge.Models; - -namespace ZitiDesktopEdge { - - public partial class MainWindow : Window { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - - public string RECOVER = "RECOVER"; - public System.Windows.Forms.NotifyIcon notifyIcon; - public string Position = "Bottom"; - private DateTime _startDate; - private System.Windows.Forms.Timer _tunnelUptimeTimer; - private DataClient serviceClient = null; - MonitorClient monitorClient = null; - private bool _isAttached = true; - private bool _isServiceInError = false; - private int _right = 75; - private int _left = 75; - private int _top = 30; - private int defaultHeight = 540; - public int NotificationsShownCount = 0; - private double _maxHeight = 800d; - public string CurrentIcon = "white"; - private string[] suffixes = { "Bps", "kBps", "mBps", "gBps", "tBps", "pBps" }; - private string _blurbUrl = ""; - - private DateTime NextNotificationTime; - private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); - - static System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); - - public static string ThisAssemblyName; - public static string ExecutionDirectory; - public static string ExpectedLogPathRoot; - public static string ExpectedLogPathUI; - public static string ExpectedLogPathServices; - - private static ZDEWViewState state; - static MainWindow() { - asm = System.Reflection.Assembly.GetExecutingAssembly(); - ThisAssemblyName = asm.GetName().Name; - state = (ZDEWViewState)Application.Current.Properties["ZDEWViewState"]; -#if DEBUG - ExecutionDirectory = @"C:\Program Files (x86)\NetFoundry, Inc\Ziti Desktop Edge"; -#else - ExecutionDirectory = Path.GetDirectoryName(asm.Location); -#endif - ExpectedLogPathRoot = Path.Combine(ExecutionDirectory, "logs"); - ExpectedLogPathUI = Path.Combine(ExpectedLogPathRoot, "UI", $"{ThisAssemblyName}.log"); - ExpectedLogPathServices = Path.Combine(ExpectedLogPathRoot, "service", $"ziti-tunneler.log"); - } - - async private void IdentityMenu_OnMessage(string message) { - await ShowBlurbAsync(message, ""); - } - - private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { - LoadIdentities(true); - } - - private List identities { - get { - return (List)Application.Current.Properties["Identities"]; - } - } - - /// - /// The MFA Toggle was toggled - /// - /// True if the toggle was on - private async void MFAToggled(bool isOn) { - if (isOn) { - ShowLoad("Generating MFA", "MFA Setup Commencing, please wait"); - - await serviceClient.EnableMFA(this.IdentityMenu.Identity.Identifier); - } else { - this.ShowMFA(IdentityMenu.Identity, 3); - } - - HideLoad(); - } - - /// - /// When a Service Client is ready to setup the MFA Authorization - /// - /// The service client - /// The MFA Event - private void ServiceClient_OnMfaEvent(object sender, MfaEvent mfa) { - HideLoad(); - this.Dispatcher.Invoke(async () => { - if (mfa.Action == "enrollment_challenge") { - string url = HttpUtility.UrlDecode(mfa.ProvisioningUrl); - string secret = HttpUtility.ParseQueryString(url)["secret"]; - this.IdentityMenu.Identity.RecoveryCodes = mfa?.RecoveryCodes?.ToArray(); - SetupMFA(this.IdentityMenu.Identity, url, secret); - } else if (mfa.Action == "auth_challenge") { - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == mfa.Identifier) { - identities[i].WasNotified = false; - identities[i].WasFullNotified = false; - identities[i].IsMFANeeded = true; - identities[i].IsTimingOut = false; - break; - } - } - } else if (mfa.Action == "enrollment_verification") { - if (mfa.Successful) { - var found = identities.Find(id => id.Identifier == mfa.Identifier); - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == mfa.Identifier) { - identities[i].WasNotified = false; - identities[i].WasFullNotified = false; - identities[i].IsMFANeeded = false; - identities[i].IsMFAEnabled = true; - identities[i].IsTimingOut = false; - identities[i].LastUpdatedTime = DateTime.Now; - for (int j = 0; j < identities[i].Services.Count; j++) { - identities[i].Services[j].TimeUpdated = DateTime.Now; - identities[i].Services[j].TimeoutRemaining = identities[i].Services[j].Timeout; - } - found = identities[i]; - found.IsMFAEnabled = true; - break; - } - } - if (this.IdentityMenu.Identity != null && this.IdentityMenu.Identity.Identifier == mfa.Identifier) this.IdentityMenu.Identity = found; - ShowMFARecoveryCodes(found); - } else { - await ShowBlurbAsync("Provided code could not be verified", ""); - } - } else if (mfa.Action == "enrollment_remove") { - if (mfa.Successful) { - var found = identities.Find(id => id.Identifier == mfa.Identifier); - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == mfa.Identifier) { - identities[i].WasNotified = false; - identities[i].WasFullNotified = false; - identities[i].IsMFAEnabled = false; - identities[i].IsMFANeeded = false; - identities[i].LastUpdatedTime = DateTime.Now; - identities[i].IsTimingOut = false; - for (int j = 0; j < identities[i].Services.Count; j++) { - identities[i].Services[j].TimeUpdated = DateTime.Now; - identities[i].Services[j].TimeoutRemaining = 0; - } - found = identities[i]; - break; - } - } - if (this.IdentityMenu.Identity != null && this.IdentityMenu.Identity.Identifier == mfa.Identifier) this.IdentityMenu.Identity = found; - await ShowBlurbAsync("MFA Disabled, Service Access Can Be Limited", ""); - } else { - await ShowBlurbAsync("MFA Removal Failed", ""); - } - } else if (mfa.Action == "mfa_auth_status") { - var found = identities.Find(id => id.Identifier == mfa.Identifier); - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == mfa.Identifier) { - identities[i].WasNotified = false; - identities[i].WasFullNotified = false; - identities[i].IsTimingOut = false; - identities[i].IsMFANeeded = !mfa.Successful; - identities[i].LastUpdatedTime = DateTime.Now; - for (int j = 0; j < identities[i].Services.Count; j++) { - identities[i].Services[j].TimeUpdated = DateTime.Now; - identities[i].Services[j].TimeoutRemaining = identities[i].Services[j].Timeout; - } - found = identities[i]; - break; - } - } - if (this.IdentityMenu.Identity != null && this.IdentityMenu.Identity.Identifier == mfa.Identifier) this.IdentityMenu.Identity = found; - // ShowBlurb("mfa authenticated: " + mfa.Successful, ""); - } else { - await ShowBlurbAsync("Unexpected error when processing MFA", ""); - logger.Error("unexpected action: " + mfa.Action); - } - - LoadIdentities(true); - }); - } - - /// - /// Show the MFA Setup Modal - /// - /// The Ziti Identity to Setup - public void SetupMFA(ZitiIdentity identity, string url, string secret) { - MFASetup.Opacity = 0; - MFASetup.Visibility = Visibility.Visible; - MFASetup.Margin = new Thickness(0, 0, 0, 0); - MFASetup.BeginAnimation(Grid.OpacityProperty, new DoubleAnimation(1, TimeSpan.FromSeconds(.3))); - MFASetup.BeginAnimation(Grid.MarginProperty, new ThicknessAnimation(new Thickness(30, 30, 30, 30), TimeSpan.FromSeconds(.3))); - MFASetup.ShowSetup(identity, url, secret); - ShowModal(); - } - - /// - /// Show the MFA Authentication Screen when it is time to authenticate - /// - /// The Ziti Identity to Authenticate - public void MFAAuthenticate(ZitiIdentity identity) { - this.ShowMFA(identity, 1); - } - - /// - /// Show MFA for the identity and set the type of screen to show - /// - /// The Identity that is currently active - /// The type of screen to show - 1 Setup, 2 Authenticate, 3 Remove MFA, 4 Regenerate Codes - private void ShowMFA(ZitiIdentity identity, int type) { - MFASetup.Opacity = 0; - MFASetup.Visibility = Visibility.Visible; - MFASetup.Margin = new Thickness(0, 0, 0, 0); - - DoubleAnimation animatin = new DoubleAnimation(1, TimeSpan.FromSeconds(.3)); - animatin.Completed += Animatin_Completed; - MFASetup.BeginAnimation(Grid.OpacityProperty, animatin); - MFASetup.BeginAnimation(Grid.MarginProperty, new ThicknessAnimation(new Thickness(30, 30, 30, 30), TimeSpan.FromSeconds(.3))); - - MFASetup.ShowMFA(identity, type); - - ShowModal(); - } - - private void Animatin_Completed(object sender, EventArgs e) { - MFASetup.AuthCode.Focusable = true; - MFASetup.AuthCode.Focus(); - } - - /// - /// Show the MFA Recovery Codes - /// - /// The Ziti Identity to Authenticate - async public void ShowMFARecoveryCodes(ZitiIdentity identity) { - if (identity.IsMFAEnabled) { - if (identity.IsMFAEnabled && identity.RecoveryCodes != null) { - MFASetup.Opacity = 0; - MFASetup.Visibility = Visibility.Visible; - MFASetup.Margin = new Thickness(0, 0, 0, 0); - MFASetup.BeginAnimation(Grid.OpacityProperty, new DoubleAnimation(1, TimeSpan.FromSeconds(.3))); - MFASetup.BeginAnimation(Grid.MarginProperty, new ThicknessAnimation(new Thickness(30, 30, 30, 30), TimeSpan.FromSeconds(.3))); - - MFASetup.ShowRecovery(identity.RecoveryCodes, identity); - - ShowModal(); - } else { - this.ShowMFA(IdentityMenu.Identity, 2); - } - } else { - await ShowBlurbAsync("MFA is not setup on this Identity", ""); - } - } - - /// - /// Show the modal, aniimating opacity - /// - private void ShowModal() { - ModalBg.Visibility = Visibility.Visible; - ModalBg.Opacity = 0; - DoubleAnimation animation = new DoubleAnimation(.8, TimeSpan.FromSeconds(.3)); - ModalBg.BeginAnimation(Grid.OpacityProperty, animation); - } - - /// - /// Close the various MFA windows - /// - /// The close button - /// The event arguments - private void CloseComplete(object sender, EventArgs e) { - MFASetup.Visibility = Visibility.Collapsed; - } - - /// - /// Hide the modal animating the opacity - /// - private void HideModal() { - DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(.3)); - animation.Completed += ModalHideComplete; - ModalBg.BeginAnimation(Grid.OpacityProperty, animation); - } - - /// - /// When the animation completes, set the visibility to avoid UI object conflicts - /// - /// The animation - /// The event - private void ModalHideComplete(object sender, EventArgs e) { - ModalBg.Visibility = Visibility.Collapsed; - } - - /// - /// Close the MFA Screen with animation - /// - private void DoClose(bool isComplete) { - DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(.3)); - ThicknessAnimation animateThick = new ThicknessAnimation(new Thickness(0, 0, 0, 0), TimeSpan.FromSeconds(.3)); - animation.Completed += CloseComplete; - MFASetup.BeginAnimation(Grid.OpacityProperty, animation); - MFASetup.BeginAnimation(Grid.MarginProperty, animateThick); - HideModal(); - if (isComplete) { - if (MFASetup.Type == 1) { - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == MFASetup.Identity.Identifier) { - identities[i] = MFASetup.Identity; - identities[i].LastUpdatedTime = DateTime.Now; - } - } - } - } - if (IdentityMenu.IsVisible) { - if (isComplete) { - if (MFASetup.Type == 2) { - ShowRecovery(IdentityMenu.Identity); - } else if (MFASetup.Type == 3) { - } else if (MFASetup.Type == 4) { - ShowRecovery(IdentityMenu.Identity); - } - } - IdentityMenu.UpdateView(); - } - LoadIdentities(true); - } - - private void AddIdentity(ZitiIdentity id) { - semaphoreSlim.Wait(); - if (!identities.Any(i => id.Identifier == i.Identifier)) { - identities.Add(id); - } - semaphoreSlim.Release(); - } - - private System.Windows.Forms.ContextMenu contextMenu; - private System.Windows.Forms.MenuItem contextMenuItem; - private System.ComponentModel.IContainer components; - public MainWindow() { - InitializeComponent(); - NextNotificationTime = DateTime.Now; - SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; - string nlogFile = Path.Combine(ExecutionDirectory, ThisAssemblyName + "-log.config"); - - - ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompat_OnActivated; - - bool byFile = false; - if (File.Exists(nlogFile)) { - LogManager.Configuration = new XmlLoggingConfiguration(nlogFile); - byFile = true; - } else { - var config = new LoggingConfiguration(); - // Targets where to log to: File and Console - var logfile = new FileTarget("logfile") { - FileName = ExpectedLogPathUI, - ArchiveEvery = FileArchivePeriod.Day, - ArchiveNumbering = ArchiveNumberingMode.Rolling, - MaxArchiveFiles = 7, - AutoFlush = true, - Layout = "[${date:format=yyyy-MM-ddTHH:mm:ss.fff}Z] ${level:uppercase=true:padding=5}\t${logger}\t${message}\t${exception:format=tostring}", - }; - var logconsole = new ConsoleTarget("logconsole"); - - // Rules for mapping loggers to targets - config.AddRule(LogLevel.Debug, LogLevel.Fatal, logconsole); - config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); - - // Apply config - LogManager.Configuration = config; - } - logger.Info("============================== UI started =============================="); - logger.Info("logger initialized"); - logger.Info(" - version : {0}", asm.GetName().Version.ToString()); - logger.Info(" - using file: {0}", byFile); - logger.Info(" - file: {0}", nlogFile); - logger.Info("========================================================================"); - - App.Current.MainWindow.WindowState = WindowState.Normal; - App.Current.MainWindow.Deactivated += MainWindow_Deactivated; - App.Current.MainWindow.Activated += MainWindow_Activated; - App.Current.Exit += Current_Exit; - App.Current.SessionEnding += Current_SessionEnding; - - - this.components = new System.ComponentModel.Container(); - this.contextMenu = new System.Windows.Forms.ContextMenu(); - this.contextMenuItem = new System.Windows.Forms.MenuItem(); - this.contextMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.contextMenuItem }); - - this.contextMenuItem.Index = 0; - this.contextMenuItem.Text = "&Close UI"; - this.contextMenuItem.Click += new System.EventHandler(this.contextMenuItem_Click); - - - notifyIcon = new System.Windows.Forms.NotifyIcon(); - notifyIcon.Visible = true; - notifyIcon.Click += TargetNotifyIcon_Click; - notifyIcon.Visible = true; - notifyIcon.BalloonTipClosed += NotifyIcon_BalloonTipClosed; - notifyIcon.MouseClick += NotifyIcon_MouseClick; - notifyIcon.ContextMenu = this.contextMenu; - - IdentityMenu.OnDetach += OnDetach; - MainMenu.OnDetach += OnDetach; - - this.MainMenu.MainWindow = this; - this.IdentityMenu.MainWindow = this; - SetNotifyIcon("white"); - - this.PreviewKeyDown += KeyPressed; - MFASetup.OnLoad += MFASetup_OnLoad; - MFASetup.OnError += MFASetup_OnError; - IdentityMenu.OnMessage += IdentityMenu_OnMessage; - } - - async private void MFASetup_OnError(string message) { - await ShowBlurbAsync(message, "", "error"); - } - - private static ToastButton feedbackToastButton = new ToastButton() - .SetContent("Click here to collect logs") - .AddArgument("action", "feedback"); - - private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e) { - this.Dispatcher.Invoke(() => { - if (e.Argument != null && e.Argument.Length > 0) { - string[] items = e.Argument.Split(';'); - if (items.Length > 0) { - string[] values = items[0].Split('='); - if (values.Length == 2) { - string identifier = values[1]; - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == identifier) { - ShowMFA(identities[i], 1); - break; - } - } - } - } - } - - ToastArguments args = ToastArguments.Parse(e.Argument); - string value = null; - if (args.TryGetValue("action", out value)) { - this.Dispatcher.Invoke(() => { - MainMenu.CollectFeedbackLogs(e, null); - }); - } - this.Show(); - this.Activate(); - }); - } - - private void KeyPressed(object sender, KeyEventArgs e) { - if (e.Key == Key.Escape) { - if (IdentityMenu.Visibility == Visibility.Visible) IdentityMenu.Visibility = Visibility.Collapsed; - else if (MainMenu.Visibility == Visibility.Visible) MainMenu.Visibility = Visibility.Collapsed; - } - } - - private void MFASetup_OnLoad(bool isComplete, string title, string message) { - if (isComplete) HideLoad(); - else ShowLoad(title, message); - } - - private void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e) { - if (notifyIcon != null) { - notifyIcon.Visible = false; - notifyIcon.Icon.Dispose(); - notifyIcon.Dispose(); - notifyIcon = null; - } - Application.Current.Shutdown(); - } - - private void Current_Exit(object sender, ExitEventArgs e) { - if (notifyIcon != null) { - notifyIcon.Visible = false; - if (notifyIcon.Icon != null) { - notifyIcon.Icon.Dispose(); - } - notifyIcon.Dispose(); - notifyIcon = null; - } - } - - private void contextMenuItem_Click(object Sender, EventArgs e) { - Application.Current.Shutdown(); - } - - private void NotifyIcon_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) { - if (e.Button == System.Windows.Forms.MouseButtons.Left) { - System.Windows.Forms.MouseEventArgs mea = (System.Windows.Forms.MouseEventArgs)e; - this.Show(); - this.Activate(); - //Do the awesome left clickness - } else if (e.Button == System.Windows.Forms.MouseButtons.Right) { - //Do the wickedy right clickness - } else { - //Some other button from the enum :) - } - } - - private void NotifyIcon_BalloonTipClosed(object sender, EventArgs e) { - var thisIcon = (System.Windows.Forms.NotifyIcon)sender; - thisIcon.Visible = false; - thisIcon.Dispose(); - } - - private void Window_MouseDown(object sender, MouseButtonEventArgs e) { - OnDetach(e); - } - - private void OnDetach(MouseButtonEventArgs e) { - if (e.ChangedButton == MouseButton.Left) { - _isAttached = false; - IdentityMenu.Arrow.Visibility = Visibility.Collapsed; - Arrow.Visibility = Visibility.Collapsed; - MainMenu.Detach(); - this.DragMove(); - } - } - - private void MainWindow_Activated(object sender, EventArgs e) { - Placement(); - this.Show(); - this.Visibility = Visibility.Visible; - this.Opacity = 1; - } - - private void MainWindow_Deactivated(object sender, EventArgs e) { - if (this._isAttached) { - this.Visibility = Visibility.Hidden; - } - } - - private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - if (notifyIcon != null) { - notifyIcon.Visible = false; - notifyIcon.Icon.Dispose(); - notifyIcon.Dispose(); - notifyIcon = null; - } - Application.Current.Shutdown(); - } - - private void SetCantDisplay(string title, string detailMessage, Visibility closeButtonVisibility) { - this.Dispatcher.Invoke(() => { - NoServiceView.Visibility = Visibility.Visible; - CloseErrorButton.IsEnabled = true; - CloseErrorButton.Visibility = closeButtonVisibility; - ErrorMsg.Content = title; - ErrorMsgDetail.Content = detailMessage; - SetNotifyIcon("red"); - _isServiceInError = true; - UpdateServiceView(); - }); - } - - private void TargetNotifyIcon_Click(object sender, EventArgs e) { - this.Show(); - this.Activate(); - Application.Current.MainWindow.Activate(); - } - - private void UpdateServiceView() { - if (_isServiceInError) { - AddIdAreaButton.Opacity = 0.1; - AddIdAreaButton.IsEnabled = false; - AddIdButton.Opacity = 0.1; - AddIdButton.IsEnabled = false; - ConnectButton.Opacity = 0.1; - StatArea.Opacity = 0.1; - } else { - AddIdAreaButton.Opacity = 1.0; - AddIdAreaButton.IsEnabled = true; - AddIdButton.Opacity = 1.0; - AddIdButton.IsEnabled = true; - StatArea.Opacity = 1.0; - ConnectButton.Opacity = 1.0; - } - TunnelConnected(!_isServiceInError); - } - - private void App_ReceiveString(string obj) { - Console.WriteLine(obj); - this.Show(); - this.Activate(); - } - - async private void MainWindow_Loaded(object sender, RoutedEventArgs e) { - - Window window = Window.GetWindow(App.Current.MainWindow); - ZitiDesktopEdge.App app = (ZitiDesktopEdge.App)App.Current; - app.ReceiveString += App_ReceiveString; - - // add a new service client - serviceClient = new DataClient("ui"); - serviceClient.OnClientConnected += ServiceClient_OnClientConnected; - serviceClient.OnClientDisconnected += ServiceClient_OnClientDisconnected; - serviceClient.OnIdentityEvent += ServiceClient_OnIdentityEvent; - serviceClient.OnMetricsEvent += ServiceClient_OnMetricsEvent; - serviceClient.OnServiceEvent += ServiceClient_OnServiceEvent; - serviceClient.OnTunnelStatusEvent += ServiceClient_OnTunnelStatusEvent; - serviceClient.OnMfaEvent += ServiceClient_OnMfaEvent; - serviceClient.OnLogLevelEvent += ServiceClient_OnLogLevelEvent; - serviceClient.OnBulkServiceEvent += ServiceClient_OnBulkServiceEvent; - serviceClient.OnNotificationEvent += ServiceClient_OnNotificationEvent; - serviceClient.OnControllerEvent += ServiceClient_OnControllerEvent; - Application.Current.Properties.Add("ServiceClient", serviceClient); - - monitorClient = new MonitorClient("ui"); - monitorClient.OnClientConnected += MonitorClient_OnClientConnected; - monitorClient.OnNotificationEvent += MonitorClient_OnInstallationNotificationEvent; - monitorClient.OnServiceStatusEvent += MonitorClient_OnServiceStatusEvent; - monitorClient.OnShutdownEvent += MonitorClient_OnShutdownEvent; - monitorClient.OnCommunicationError += MonitorClient_OnCommunicationError; - monitorClient.OnReconnectFailure += MonitorClient_OnReconnectFailure; - Application.Current.Properties.Add("MonitorClient", monitorClient); - - Application.Current.Properties.Add("Identities", new List()); - MainMenu.OnAttachmentChange += AttachmentChanged; - MainMenu.OnLogLevelChanged += LogLevelChanged; - MainMenu.OnShowBlurb += MainMenu_OnShowBlurb; - IdentityMenu.OnError += IdentityMenu_OnError; - - try { - await serviceClient.ConnectAsync(); - await serviceClient.WaitForConnectionAsync(); - } catch /*ignored for now (Exception ex) */ - { - ShowServiceNotStarted(); - serviceClient.Reconnect(); - } - - try { - await monitorClient.ConnectAsync(); - await monitorClient.WaitForConnectionAsync(); - } catch /*ignored for now (Exception ex) */ - { - monitorClient.Reconnect(); - } - - IdentityMenu.OnForgot += IdentityForgotten; - Placement(); - } - - private void MonitorClient_OnCommunicationError(object sender, Exception e) { - string msg = "Communication Error with monitor?"; - ShowError(msg, e.Message); - } - - private void MainMenu_OnShowBlurb(string message) { - _ = ShowBlurbAsync(message, "", "info"); - } - - private void ServiceClient_OnBulkServiceEvent(object sender, BulkServiceEvent e) { - var found = identities.Find(id => id.Identifier == e.Identifier); - if (found == null) { - logger.Warn($"{e.Action} service event for {e.Identifier} but the provided identity identifier was not found!"); - return; - } else { - if (e.RemovedServices != null) { - foreach (var removed in e.RemovedServices) { - removeService(found, removed); - } - } - if (e.AddedServices != null) { - foreach (var added in e.AddedServices) { - addService(found, added); - } - } - LoadIdentities(true); - this.Dispatcher.Invoke(() => { - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; - if (deets.IsVisible) { - deets.UpdateView(); - } - }); - } - } - - private void ServiceClient_OnNotificationEvent(object sender, NotificationEvent e) { - var displayMFARequired = false; - var displayMFATimout = false; - foreach (var notification in e.Notification) { - var found = identities.Find(id => id.Identifier == notification.Identifier); - if (found == null) { - logger.Warn($"{e.Op} event for {notification.Identifier} but the provided identity identifier was not found!"); - continue; - } else { - found.TimeoutMessage = notification.Message; - found.MaxTimeout = notification.MfaMaximumTimeout; - found.MinTimeout = notification.MfaMinimumTimeout; - - if (notification.MfaMinimumTimeout == 0) { - // display mfa token icon - displayMFARequired = true; - } else { - displayMFATimout = true; - } - - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == found.Identifier) { - identities[i] = found; - break; - } - } - } - } - - // we may need to display mfa icon, based on the timer in UI, remove found.MFAInfo.ShowMFA setting in this function. - // the below function can show mfa icon even after user authenticates successfully, in race conditions - if (displayMFARequired || displayMFATimout) { - this.Dispatcher.Invoke(() => { - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; - if (deets.IsVisible) { - deets.UpdateView(); - } - }); - } - LoadIdentities(true); - } - - private void ServiceClient_OnControllerEvent(object sender, ControllerEvent e) { - logger.Debug($"==== ControllerEvent : action:{e.Action} identifier:{e.Identifier}"); - // commenting this block, because when it receives the disconnected events, identities are disabled and - // it is not allowing me to click/perform any operation on the identity - // the color of the title is also too dark, and it is not clearly visible, when the identity is disconnected - /* if (e.Action == "connected") { - var found = identities.Find(i => i.Identifier == e.Identifier); - found.IsConnected = true; - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == found.Identifier) { - identities[i] = found; - break; - } - } - LoadIdentities(true); - } else if (e.Action == "disconnected") { - var found = identities.Find(i => i.Identifier == e.Identifier); - found.IsConnected = false; - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == found.Identifier) { - identities[i] = found; - break; - } - } - LoadIdentities(true); - } */ - } - - - string nextVersionStr = null; - private void MonitorClient_OnReconnectFailure(object sender, object e) { - logger.Trace("OnReconnectFailure triggered"); - if (nextVersionStr == null) { - // check for the current version - nextVersionStr = "checking for update"; - Version nextVersion = GithubAPI.GetVersion(GithubAPI.GetJson(GithubAPI.ProdUrl)); - nextVersionStr = nextVersion.ToString(); - Version currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; //fetch from ziti? - - int compare = currentVersion.CompareTo(nextVersion); - if (compare < 0) { - MainMenu.SetAppUpgradeAvailableText("Upgrade available: " + nextVersionStr); - logger.Info("upgrade is available. Published version: {} is newer than the current version: {}", nextVersion, currentVersion); - //UpgradeAvailable(); - } else if (compare > 0) { - logger.Info("the version installed: {0} is newer than the released version: {1}", currentVersion, nextVersion); - MainMenu.SetAppIsNewer("This version is newer than the latest: " + nextVersionStr); - } else { - logger.Info("Current version installed: {0} is the same as the latest released version {1}", currentVersion, nextVersion); - MainMenu.SetAppUpgradeAvailableText(""); - } - } - } - - private void MonitorClient_OnShutdownEvent(object sender, StatusEvent e) { - logger.Info("The monitor has indicated the application should shut down."); - this.Dispatcher.Invoke(() => { - Application.Current.Shutdown(); - }); - } - - private void MonitorClient_OnServiceStatusEvent(object sender, MonitorServiceStatusEvent evt) { - this.Dispatcher.Invoke(() => { - try { - if (evt.Message?.ToLower() == "upgrading") { - logger.Info("The monitor has indicated an upgrade is in progress. Shutting down the UI"); - UpgradeSentinel.StartUpgradeSentinel(); - - App.Current.Exit -= Current_Exit; - logger.Info("Removed Current_Exit handler"); - notifyIcon.Visible = false; - notifyIcon.Icon.Dispose(); - notifyIcon.Dispose(); - Application.Current.Shutdown(); - return; - } - SetAutomaticUpdateEnabled(evt.AutomaticUpgradeDisabled, evt.AutomaticUpgradeURL); - if (evt.Code != 0) { - logger.Error("CODE: " + evt.Code); - if (MainMenu.ShowUnexpectedFailure) { - ShowToast("The data channel has stopped unexpectedly", $"If this keeps happening please collect logs and report the issue.", feedbackToastButton); - } - } - MainMenu.ShowUpdateAvailable(); - logger.Debug("MonitorClient_OnServiceStatusEvent: {0}", evt.Status); - Application.Current.Properties["ReleaseStream"] = evt.ReleaseStream; - - ServiceControllerStatus status = (ServiceControllerStatus)Enum.Parse(typeof(ServiceControllerStatus), evt.Status); - - switch (status) { - case ServiceControllerStatus.Running: - logger.Info("Service is started"); - break; - case ServiceControllerStatus.Stopped: - logger.Info("Service is stopped"); - ShowServiceNotStarted(); - break; - case ServiceControllerStatus.StopPending: - logger.Info("Service is stopping..."); - - this.Dispatcher.Invoke(async () => { - SetCantDisplay("The Service is Stopping", "Please wait while the service stops", Visibility.Visible); - await WaitForServiceToStop(DateTime.Now + TimeSpan.FromSeconds(30)); - }); - break; - case ServiceControllerStatus.StartPending: - logger.Info("Service is starting..."); - break; - case ServiceControllerStatus.PausePending: - logger.Warn("UNEXPECTED STATUS: PausePending"); - break; - case ServiceControllerStatus.Paused: - logger.Warn("UNEXPECTED STATUS: Paused"); - break; - default: - logger.Warn("UNEXPECTED STATUS: {0}", evt.Status); - break; - } - } catch (Exception ex) { - logger.Warn(ex, "unexpected exception in MonitorClient_OnServiceStatusEvent? {0}", ex.Message); - } - }); - } - - public void SetAutomaticUpdateEnabled(string enabled, string url) { - this.Dispatcher.Invoke(() => { - state.AutomaticUpdatesDisabled = bool.Parse(enabled); - state.AutomaticUpdateURL = url; - }); - } - - private void MonitorClient_OnInstallationNotificationEvent(object sender, InstallationNotificationEvent evt) { - this.Dispatcher.Invoke(() => { - logger.Debug("MonitorClient_OnInstallationNotificationEvent: {0}", evt.Message); - switch (evt.Message?.ToLower()) { - case "installationupdate": - logger.Debug("Installation Update is available - {0}", evt.ZDEVersion); - var remaining = evt.InstallTime - DateTime.Now; - - state.PendingUpdate.Version = evt.ZDEVersion; - state.PendingUpdate.InstallTime = evt.InstallTime; - state.UpdateAvailable = true; - SetAutomaticUpdateEnabled(evt.AutomaticUpgradeDisabled, evt.AutomaticUpgradeURL); - MainMenu.ShowUpdateAvailable(); - AlertCanvas.Visibility = Visibility.Visible; - - if (isToastEnabled()) { - if (!state.AutomaticUpdatesDisabled) { - if (remaining.TotalSeconds < 60) { - //this is an immediate update - show a different message - ShowToast("Ziti Desktop Edge will initiate auto installation in the next minute!"); - } else { - if (DateTime.Now > NextNotificationTime) { - ShowToast($"Update {evt.ZDEVersion} is available for Ziti Desktop Edge and will be automatically installed by " + evt.InstallTime); - NextNotificationTime = DateTime.Now + evt.NotificationDuration; - } else { - logger.Debug("Skipping notification. Time until next notification {} seconds which is at {}", (int)((NextNotificationTime - DateTime.Now).TotalSeconds), NextNotificationTime); - } - } - } else { - ShowToast("New version available", $"Version {evt.ZDEVersion} is available for Ziti Desktop Edge", null); - } - SetNotifyIcon(""); - // display a tag in UI and a button for the update software - } - break; - case "configuration changed": - break; - default: - logger.Debug("unexpected event type?"); - break; - } - }); - } - - private bool isToastEnabled() { - bool result; - //only show notifications once if automatic updates are disabled - if (NotificationsShownCount == 0) { - result = true; //regardless - if never notified, always return true - } else { - result = !state.AutomaticUpdatesDisabled; - } - return result; - } - - private void ShowToast(string header, string message, ToastButton button) { - try { - logger.Debug("showing toast: {} {}", header, message); - var builder = new ToastContentBuilder() - .AddArgument("notbutton", "click") - .AddText(header) - .AddText(message); - if (button != null) { - builder.AddButton(button); - } - builder.Show(); - NotificationsShownCount++; - } catch { - logger.Warn("couldn't show toast: {} {}", header, message); - } - } - - - private void ShowToast(string message) { - ShowToast("Important Notice", message, null); - } - - async private Task WaitForServiceToStop(DateTime until) { - //continually poll for the service to stop. If it is stuck - ask the user if they want to try to force - //close the service - while (DateTime.Now < until) { - await Task.Delay(250); - MonitorServiceStatusEvent resp = await monitorClient.StatusAsync(); - if (resp.IsStopped()) { - // good - that's what we are waiting for... - return; - } else { - // bad - not stopped yet... - logger.Debug("Waiting for service to stop... Still not stopped yet. Status: {0}", resp.Status); - } - } - // real bad - means it's stuck probably. Ask the user if they want to try to force it... - logger.Warn("Waiting for service to stop... Service did not reach stopped state in the expected amount of time."); - SetCantDisplay("The Service Appears Stuck", "Would you like to try to force close the service?", Visibility.Visible); - CloseErrorButton.Content = "Force Quit"; - CloseErrorButton.Click -= CloseError; - CloseErrorButton.Click += ForceQuitButtonClick; - } - - async private void ForceQuitButtonClick(object sender, RoutedEventArgs e) { - MonitorServiceStatusEvent status = await monitorClient.ForceTerminateAsync(); - if (status.IsStopped()) { - //good - CloseErrorButton.Click += CloseError; //reset the close button... - CloseErrorButton.Click -= ForceQuitButtonClick; - } else { - //bad... - SetCantDisplay("The Service Is Still Running", "Current status is: " + status.Status, Visibility.Visible); - } - } - - async private void StartZitiService(object sender, RoutedEventArgs e) { - try { - ShowLoad("Starting", "Starting the data service"); - logger.Info("StartZitiService"); - var r = await monitorClient.StartServiceAsync(); - if (r.Code != 0) { - logger.Debug("ERROR: {0} : {1}", r.Message, r.Error); - } else { - logger.Info("Service started!"); - CloseErrorButton.Click -= StartZitiService; - CloseError(null, null); - } - } catch (MonitorServiceException me) { - logger.Warn("the monitor service appears offline. {0}", me); - CloseErrorButton.IsEnabled = true; - HideLoad(); - ShowError("Error Starting Service", "The monitor service is offline"); - } catch (Exception ex) { - logger.Error(ex, "UNEXPECTED ERROR!"); - CloseErrorButton.IsEnabled = true; - HideLoad(); - ShowError("Unexpected Error", "Code 2:" + ex.Message); - } - CloseErrorButton.IsEnabled = true; - // HideLoad(); - } - - private void ShowServiceNotStarted() { - TunnelConnected(false); - LoadIdentities(true); - } - - private void MonitorClient_OnClientConnected(object sender, object e) { - logger.Debug("MonitorClient_OnClientConnected"); - MainMenu.SetAppUpgradeAvailableText(""); - } - - async private Task LogLevelChanged(string level) { - int logsSet = 0; - try { - await serviceClient.SetLogLevelAsync(level); - logsSet++; - await monitorClient.SetLogLevelAsync(level); - logsSet++; - Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(level); - return true; - } catch (Exception ex) { - logger.Error(ex, "Unexpected error. logsSet: {0}", logsSet); - if (logsSet > 1) { - await ShowBlurbAsync("Unexpected error setting logs?", ""); - } else if (logsSet > 0) { - await ShowBlurbAsync("Failed to set monitor client log level", ""); - } else { - await ShowBlurbAsync("Failed to set log levels", ""); - } - } - return false; - } - - private void IdentityMenu_OnError(string message) { - ShowError("Identity Error", message); - } - - private void ServiceClient_OnClientConnected(object sender, object e) { - this.Dispatcher.Invoke(() => { - MainMenu.Connected(); - NoServiceView.Visibility = Visibility.Collapsed; - _isServiceInError = false; - UpdateServiceView(); - SetNotifyIcon("white"); - LoadIdentities(true); - }); - } - - private void ServiceClient_OnClientDisconnected(object sender, object e) { - this.Dispatcher.Invoke(() => { - IdentityMenu.Visibility = Visibility.Collapsed; - MFASetup.Visibility = Visibility.Collapsed; - HideModal(); - MainMenu.Disconnected(); - for (int i = 0; i < IdList.Children.Count; i++) { - IdentityItem item = (IdentityItem)IdList.Children[i]; - item.StopTimers(); - } - IdList.Children.Clear(); - if (e != null) { - logger.Debug(e.ToString()); - } - //SetCantDisplay("Start the Ziti Tunnel Service to continue"); - SetNotifyIcon("red"); - ShowServiceNotStarted(); - }); - } - - /// - /// If an identity gets added late, execute this. - /// - /// Do not update services for identity events - /// - /// The sending service - /// The identity event - private void ServiceClient_OnIdentityEvent(object sender, IdentityEvent e) { - if (e == null) return; - - ZitiIdentity zid = ZitiIdentity.FromClient(e.Id); - logger.Debug($"==== IdentityEvent : action:{e.Action} identifer:{e.Id.Identifier} name:{e.Id.Name} "); - - this.Dispatcher.Invoke(async () => { - if (e.Action == "added") { - var found = identities.Find(i => i.Identifier == e.Id.Identifier); - if (found == null) { - AddIdentity(zid); - LoadIdentities(true); - } else { - // means we likely are getting an update for some reason. compare the identities and use the latest info - if (zid.Name != null && zid.Name.Length > 0) found.Name = zid.Name; - if (zid.ControllerUrl != null && zid.ControllerUrl.Length > 0) found.ControllerUrl = zid.ControllerUrl; - if (zid.ContollerVersion != null && zid.ContollerVersion.Length > 0) found.ContollerVersion = zid.ContollerVersion; - found.IsEnabled = zid.IsEnabled; - found.IsMFAEnabled = e.Id.MfaEnabled; - found.IsConnected = true; - found.NeedsExtAuth = e.Id.NeedsExtAuth; - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == found.Identifier) { - identities[i] = found; - break; - } - } - LoadIdentities(true); - } - } else if (e.Action == "updated") { - //this indicates that all updates have been sent to the UI... wait for 2 seconds then trigger any ui updates needed - await Task.Delay(2000); - LoadIdentities(true); - } else if (e.Action == "connected") { - var found = identities.Find(i => i.Identifier == e.Id.Identifier); - found.IsConnected = true; - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == found.Identifier) { - identities[i] = found; - break; - } - } - LoadIdentities(true); - } else if (e.Action == "disconnected") { - var found = identities.Find(i => i.Identifier == e.Id.Identifier); - found.IsConnected = false; - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == found.Identifier) { - identities[i] = found; - break; - } - } - LoadIdentities(true); - } else if (e.Action == "needs_ext_login") { - logger.Debug("needs_ext_login action received"); //handled through identity event at the moment (forever?) - } else { - logger.Warn("unexpected action received: {}", e.Action); - IdentityForgotten(ZitiIdentity.FromClient(e.Id)); - } - }); - logger.Debug($"IDENTITY EVENT. Action: {e.Action} identifier: {zid.Identifier}"); - } - - private void ServiceClient_OnMetricsEvent(object sender, List ids) { - if (ids != null) { - long totalUp = 0; - long totalDown = 0; - foreach (var id in ids) { - //logger.Debug($"==== MetricsEvent : id {id.Name} down: {id.Metrics.Down} up:{id.Metrics.Up}"); - if (id?.Metrics != null) { - totalDown += id.Metrics.Down; - totalUp += id.Metrics.Up; - } - } - this.Dispatcher.Invoke(() => { - SetSpeed(totalUp, UploadSpeed, UploadSpeedLabel); - SetSpeed(totalDown, DownloadSpeed, DownloadSpeedLabel); - }); - } - } - - public void SetSpeed(decimal bytes, Label speed, Label speedLabel) { - int counter = 0; - while (Math.Round(bytes / 1024) >= 1) { - bytes = bytes / 1024; - counter++; - } - speed.Content = bytes.ToString("0.0"); - speedLabel.Content = suffixes[counter]; - } - - private void ServiceClient_OnServiceEvent(object sender, ServiceEvent e) { - if (e == null) return; - - logger.Debug($"==== ServiceEvent : action:{e.Action} identifier:{e.Identifier} name:{e.Service.Name} "); - var found = identities.Find(id => id.Identifier == e.Identifier); - if (found == null) { - logger.Debug($"{e.Action} service event for {e.Service.Name} but the provided identity identifier {e.Identifier} is not found!"); - return; - } - - if (e.Action == "added") { - addService(found, e.Service); - } else { - removeService(found, e.Service); - } - LoadIdentities(true); - this.Dispatcher.Invoke(() => { - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; - if (deets.IsVisible) { - deets.UpdateView(); - } - }); - } - - private void addService(ZitiIdentity found, Service added) { - ZitiService zs = new ZitiService(added); - var svc = found.Services.Find(s => s.Name == zs.Name); - if (svc == null) { - logger.Debug("Service Added: " + zs.Name); - found.Services.Add(zs); - if (zs.HasFailingPostureCheck()) { - found.HasServiceFailingPostureCheck = true; - if (zs.PostureChecks.Any(p => !p.IsPassing && p.QueryType == "MFA")) { - found.IsMFANeeded = true; - } - } - } else { - logger.Debug("the service named " + zs.Name + " is already accounted for on this identity."); - } - } - - private void removeService(ZitiIdentity found, Service removed) { - logger.Debug("removing the service named: {0}", removed.Name); - found.Services.RemoveAll(s => s.Name == removed.Name); - } - - private void ServiceClient_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { - if (e == null) return; //just skip it for now... - logger.Debug($"==== TunnelStatusEvent: "); - Application.Current.Properties.Remove("CurrentTunnelStatus"); - Application.Current.Properties.Add("CurrentTunnelStatus", e.Status); - e.Status.Dump(Console.Out); - this.Dispatcher.Invoke(() => { - /*if (e.ApiVersion != DataClient.EXPECTED_API_VERSION) { - SetCantDisplay("Version mismatch!", "The version of the Service is not compatible", Visibility.Visible); - return; - }*/ - this.MainMenu.LogLevel = e.Status.LogLevel; - Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(e.Status.LogLevel); - InitializeTimer((int)e.Status.Duration); - LoadStatusFromService(e.Status); - LoadIdentities(true); - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; - if (deets.IsVisible) { - deets.UpdateView(); - } - }); - } - - private void ServiceClient_OnLogLevelEvent(object sender, LogLevelEvent e) { - if (e.LogLevel != null) { - SetLogLevel_monitor(e.LogLevel); - this.Dispatcher.Invoke(() => { - this.MainMenu.LogLevel = e.LogLevel; - Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(e.LogLevel); - }); - } - } - - async private void SetLogLevel_monitor(string loglevel) { - await monitorClient.SetLogLevelAsync(loglevel); - } - - private void IdentityForgotten(ZitiIdentity forgotten) { - ZitiIdentity idToRemove = null; - foreach (var id in identities) { - if (id.Identifier == forgotten.Identifier) { - idToRemove = id; - break; - } - } - identities.Remove(idToRemove); - LoadIdentities(false); - } - - private void AttachmentChanged(bool attached) { - _isAttached = attached; - if (!_isAttached) { - SetLocation(); - } - Placement(); - MainMenu.Visibility = Visibility.Collapsed; - } - - private void LoadStatusFromService(TunnelStatus status) { - //clear any identities - this.identities.Clear(); - - if (status != null) { - _isServiceInError = false; - UpdateServiceView(); - NoServiceView.Visibility = Visibility.Collapsed; - SetNotifyIcon("green"); - if (!Application.Current.Properties.Contains("ip")) { - Application.Current.Properties.Add("ip", status?.IpInfo?.Ip); - } else { - Application.Current.Properties["ip"] = status?.IpInfo?.Ip; - } - if (!Application.Current.Properties.Contains("subnet")) { - Application.Current.Properties.Add("subnet", status?.IpInfo?.Subnet); - } else { - Application.Current.Properties["subnet"] = status?.IpInfo?.Subnet; - } - if (!Application.Current.Properties.Contains("mtu")) { - Application.Current.Properties.Add("mtu", status?.IpInfo?.MTU); - } else { - Application.Current.Properties["mtu"] = status?.IpInfo?.MTU; - } - if (!Application.Current.Properties.Contains("dns")) { - Application.Current.Properties.Add("dns", status?.IpInfo?.DNS); - } else { - Application.Current.Properties["dns"] = status?.IpInfo?.DNS; - } - if (!Application.Current.Properties.Contains("dnsenabled")) { - Application.Current.Properties.Add("dnsenabled", status?.AddDns); - } else { - Application.Current.Properties["dnsenabled"] = status?.AddDns; - } - - string key = "ApiPageSize"; - if (!Application.Current.Properties.Contains(key)) { - Application.Current.Properties.Add(key, status?.ApiPageSize); - } else { - Application.Current.Properties[key] = status?.ApiPageSize; - } - - foreach (var id in status.Identities) { - updateViewWithIdentity(id); - } - LoadIdentities(true); - } else { - ShowServiceNotStarted(); - } - } - - private void updateViewWithIdentity(Identity id) { - var zid = ZitiIdentity.FromClient(id); - foreach (var i in identities) { - if (i.Identifier == zid.Identifier) { - identities.Remove(i); - break; - } - } - identities.Add(zid); - } - - private bool IsTimingOut() { - if (identities != null) { - for (int i = 0; i < identities.Count; i++) { - if (identities[i].IsTimingOut) return true; - } - } - return false; - } - - private bool IsTimedOut() { - if (identities != null) { - return identities.Any(i => i.IsTimedOut); - } - return false; - } - - private void SetNotifyIcon(string iconPrefix) { - if (iconPrefix != "") CurrentIcon = iconPrefix; - string icon = "pack://application:,,/Assets/Images/ziti-" + CurrentIcon; - if (state.UpdateAvailable) { - icon += "-update"; - } else { - if (IsTimedOut()) { - icon += "-mfa"; - } else { - if (IsTimingOut()) { - icon += "-timer"; - } - } - } - icon += ".ico"; - var iconUri = new Uri(icon); - Stream iconStream = Application.GetResourceStream(iconUri).Stream; - notifyIcon.Icon = new Icon(iconStream); - - Application.Current.MainWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconUri); - } - - private void LoadIdentities(Boolean repaint) { - this.Dispatcher.Invoke(() => { - for (int i = 0; i < IdList.Children.Count; i++) { - IdentityItem item = (IdentityItem)IdList.Children[i]; - item.StopTimers(); - } - IdList.Children.Clear(); - IdList.Height = 0; - var desktopWorkingArea = SystemParameters.WorkArea; - if (_maxHeight > (desktopWorkingArea.Height - 10)) _maxHeight = desktopWorkingArea.Height - 10; - if (_maxHeight < 100) _maxHeight = 100; - IdList.MaxHeight = _maxHeight - 520; - ZitiIdentity[] ids = identities.OrderBy(i => (i.Name != null) ? i.Name.ToLower() : i.Name).ToArray(); - MainMenu.SetupIdList(ids); - if (ids.Length > 0 && serviceClient.Connected) { - double height = defaultHeight + (ids.Length * 60); - if (height > _maxHeight) height = _maxHeight; - this.Height = height; - IdentityMenu.SetHeight(this.Height - 160); - MainMenu.IdentitiesButton.Visibility = Visibility.Visible; - foreach (var id in ids) { - IdentityItem idItem = new IdentityItem(); - - idItem.ToggleStatus.IsEnabled = id.IsEnabled; - if (id.IsEnabled) idItem.ToggleStatus.Content = "ENABLED"; - else idItem.ToggleStatus.Content = "DISABLED"; - - idItem.Authenticate += IdItem_Authenticate; - idItem.OnStatusChanged += Id_OnStatusChanged; - idItem.Identity = id; - idItem.IdentityChanged += IdItem_IdentityChanged; - - if (repaint) { - idItem.RefreshUI(); - } - - IdList.Children.Add(idItem); - - if (IdentityMenu.Visibility == Visibility.Visible) { - if (id.Identifier == IdentityMenu.Identity.Identifier) IdentityMenu.Identity = id; - } - } - DoubleAnimation animation = new DoubleAnimation((double)(ids.Length * 64), TimeSpan.FromSeconds(.2)); - IdList.BeginAnimation(FrameworkElement.HeightProperty, animation); - IdListScroller.Visibility = Visibility.Visible; - } else { - this.Height = defaultHeight; - MainMenu.IdentitiesButton.Visibility = Visibility.Collapsed; - IdListScroller.Visibility = Visibility.Visible; - - } - AddIdButton.Visibility = Visibility.Visible; - AddIdAreaButton.Visibility = Visibility.Visible; - - Placement(); - SetNotifyIcon(""); - }); - } - - private void IdItem_IdentityChanged(ZitiIdentity identity) { - for (int i = 0; i < identities.Count; i++) { - if (identities[i].Identifier == identity.Identifier) { - identities[i] = identity; - break; - } - } - SetNotifyIcon(""); - } - - private void IdItem_Authenticate(ZitiIdentity identity) { - ShowAuthenticate(identity); - } - - private void Id_OnStatusChanged(bool attached) { - for (int i = 0; i < IdList.Children.Count; i++) { - IdentityItem item = IdList.Children[i] as IdentityItem; - if (item.ToggleSwitch.Enabled) break; - } - } - - private void TunnelConnected(bool isConnected) { - this.Dispatcher.Invoke(() => { - if (isConnected) { - ConnectButton.Visibility = Visibility.Collapsed; - DisconnectButton.Visibility = Visibility.Visible; - MainMenu.Connected(); - HideLoad(); - SetNotifyIcon("green"); - } else { - ConnectButton.Visibility = Visibility.Visible; - DisconnectButton.Visibility = Visibility.Collapsed; - IdentityMenu.Visibility = Visibility.Collapsed; - MainMenu.Visibility = Visibility.Collapsed; - HideBlurb(); - MainMenu.Disconnected(); - DownloadSpeed.Content = "0.0"; - UploadSpeed.Content = "0.0"; - } - }); - } - - private void SetLocation() { - var desktopWorkingArea = SystemParameters.WorkArea; - - var height = MainView.ActualHeight; - IdentityMenu.MainHeight = MainView.ActualHeight; - MainMenu.MainHeight = MainView.ActualHeight; - - Rectangle trayRectangle = WinAPI.GetTrayRectangle(); - if (trayRectangle.Top < 20) { - this.Position = "Top"; - this.Top = desktopWorkingArea.Top + _top; - this.Left = desktopWorkingArea.Right - this.Width - _right; - Arrow.SetValue(Canvas.TopProperty, (double)0); - Arrow.SetValue(Canvas.LeftProperty, (double)185); - MainMenu.Arrow.SetValue(Canvas.TopProperty, (double)0); - MainMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); - IdentityMenu.Arrow.SetValue(Canvas.TopProperty, (double)0); - IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); - } else if (trayRectangle.Left < 20) { - this.Position = "Left"; - this.Left = _left; - this.Top = desktopWorkingArea.Bottom - this.ActualHeight - 75; - Arrow.SetValue(Canvas.TopProperty, height - 200); - Arrow.SetValue(Canvas.LeftProperty, (double)0); - MainMenu.Arrow.SetValue(Canvas.TopProperty, height - 200); - MainMenu.Arrow.SetValue(Canvas.LeftProperty, (double)0); - IdentityMenu.Arrow.SetValue(Canvas.TopProperty, height - 200); - IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, (double)0); - } else if (desktopWorkingArea.Right == (double)trayRectangle.Left) { - this.Position = "Right"; - this.Left = desktopWorkingArea.Right - this.Width - 20; - this.Top = desktopWorkingArea.Bottom - height - 75; - Arrow.SetValue(Canvas.TopProperty, height - 100); - Arrow.SetValue(Canvas.LeftProperty, this.Width - 30); - MainMenu.Arrow.SetValue(Canvas.TopProperty, height - 100); - MainMenu.Arrow.SetValue(Canvas.LeftProperty, this.Width - 30); - IdentityMenu.Arrow.SetValue(Canvas.TopProperty, height - 100); - IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, this.Width - 30); - } else { - this.Position = "Bottom"; - this.Left = desktopWorkingArea.Right - this.Width - 75; - this.Top = desktopWorkingArea.Bottom - height; - Arrow.SetValue(Canvas.TopProperty, height - 35); - Arrow.SetValue(Canvas.LeftProperty, (double)185); - MainMenu.Arrow.SetValue(Canvas.TopProperty, height - 35); - MainMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); - IdentityMenu.Arrow.SetValue(Canvas.TopProperty, height - 35); - IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); - } - } - public void Placement() { - if (_isAttached) { - Arrow.Visibility = Visibility.Visible; - IdentityMenu.Arrow.Visibility = Visibility.Visible; - SetLocation(); - } else { - IdentityMenu.Arrow.Visibility = Visibility.Collapsed; - Arrow.Visibility = Visibility.Collapsed; - } - } - - private void OpenIdentity(ZitiIdentity identity) { - IdentityMenu.Identity = identity; - } - - private void ShowMenu(object sender, MouseButtonEventArgs e) { - MainMenu.Visibility = Visibility.Visible; - } - - async private void AddIdentity(object sender, MouseButtonEventArgs e) { - UIModel.HideOnLostFocus = false; - Microsoft.Win32.OpenFileDialog jwtDialog = new Microsoft.Win32.OpenFileDialog(); - UIModel.HideOnLostFocus = true; - jwtDialog.DefaultExt = ".jwt"; - jwtDialog.Filter = "Ziti Identities (*.jwt)|*.jwt"; - - if (jwtDialog.ShowDialog() == true) { - ShowLoad("Adding Identity", "Please wait while the identity is added"); - string fileContent = File.ReadAllText(jwtDialog.FileName); - - try { - Identity createdId = await serviceClient.AddIdentityAsync(System.IO.Path.GetFileName(jwtDialog.FileName), false, fileContent); - - if (createdId != null) { - var zid = ZitiIdentity.FromClient(createdId); - AddIdentity(zid); - LoadIdentities(true); - await serviceClient.IdentityOnOffAsync(createdId.Identifier, true); - }/* else { - ShowError("Identity Error", "Identity Id was null, please try again"); - }*/ - } catch (ServiceException se) { - ShowError(se.Message, se.AdditionalInfo); - } catch (Exception ex) { - ShowError("Unexpected Error", "Code 2:" + ex.Message); - } - HideLoad(); - } - } - - private void OnTimedEvent(object sender, EventArgs e) { - TimeSpan span = (DateTime.Now - _startDate); - int hours = span.Hours; - int minutes = span.Minutes; - int seconds = span.Seconds; - var hoursString = (hours > 9) ? hours.ToString() : "0" + hours; - var minutesString = (minutes > 9) ? minutes.ToString() : "0" + minutes; - var secondsString = (seconds > 9) ? seconds.ToString() : "0" + seconds; - ConnectedTime.Content = hoursString + ":" + minutesString + ":" + secondsString; - } - - private void InitializeTimer(int millisAgoStarted) { - _startDate = DateTime.Now.Subtract(new TimeSpan(0, 0, 0, 0, millisAgoStarted)); - _tunnelUptimeTimer = new System.Windows.Forms.Timer(); - _tunnelUptimeTimer.Interval = 100; - _tunnelUptimeTimer.Tick += OnTimedEvent; - _tunnelUptimeTimer.Enabled = true; - _tunnelUptimeTimer.Start(); - } - - async private Task DoConnectAsync() { - try { - SetNotifyIcon("green"); - TunnelConnected(true); - - for (int i = 0; i < identities.Count; i++) { - await serviceClient.IdentityOnOffAsync(identities[i].Identifier, true); - } - for (int i = 0; i < IdList.Children.Count; i++) { - IdentityItem item = IdList.Children[i] as IdentityItem; - item._identity.IsEnabled = true; - item.RefreshUI(); - } - } catch (ServiceException se) { - ShowError("Error Occurred", se.Message + " " + se.AdditionalInfo); - } catch (Exception ex) { - ShowError("Unexpected Error", "Code 3:" + ex.Message); - } - } - - async private void Disconnect(object sender, RoutedEventArgs e) { - try { - ShowLoad("Disabling Service", "Please wait for the service to stop."); - var r = await monitorClient.StopServiceAsync(); - if (r.Code != 0) { - logger.Warn("ERROR: Error:{0}, Message:{1}", r.Error, r.Message); - } else { - logger.Info("Service stopped!"); - SetNotifyIcon("white"); - } - } catch (MonitorServiceException me) { - logger.Warn("the monitor service appears offline. {0}", me); - ShowError("Error Disabling Service", "The monitor service is offline"); - } catch (Exception ex) { - logger.Error(ex, "unexpected error: {0}", ex.Message); - ShowError("Error Disabling Service", "An error occurred while trying to disable the data service. Is the monitor service running?"); - } - HideLoad(); - } - - internal void ShowLoad(string title, string msg) { - this.Dispatcher.Invoke(() => { - LoadingDetails.Text = msg; - LoadingTitle.Content = title; - LoadProgress.IsIndeterminate = true; - LoadingScreen.Visibility = Visibility.Visible; - UpdateLayout(); - }); - } - - internal void HideLoad() { - this.Dispatcher.Invoke(() => { - LoadingScreen.Visibility = Visibility.Collapsed; - LoadProgress.IsIndeterminate = false; - }); - } - - private void FormFadeOut_Completed(object sender, EventArgs e) { - closeCompleted = true; - } - private bool closeCompleted = false; - private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - if (!closeCompleted) { - FormFadeOut.Begin(); - e.Cancel = true; - } - } - - public void ShowError(string title, string message) { - this.Dispatcher.Invoke(() => { - ErrorTitle.Content = title; - ErrorDetails.Text = message; - ErrorView.Visibility = Visibility.Visible; - }); - } - - private void CloseError(object sender, RoutedEventArgs e) { - this.Dispatcher.Invoke(() => { - ErrorView.Visibility = Visibility.Collapsed; - NoServiceView.Visibility = Visibility.Collapsed; - CloseErrorButton.IsEnabled = true; - }); - } - - private void CloseApp(object sender, RoutedEventArgs e) { - Application.Current.Shutdown(); - } - - private void MainUI_Deactivated(object sender, EventArgs e) { - if (this._isAttached) { -#if DEBUG - logger.Debug("debug is enabled - windows pinned"); -#else - this.Visibility = Visibility.Collapsed; -#endif - } - } - - private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) { - Placement(); - } - - int cur = 0; - LogLevelEnum[] levels = new LogLevelEnum[] { LogLevelEnum.FATAL, LogLevelEnum.ERROR, LogLevelEnum.WARN, LogLevelEnum.INFO, LogLevelEnum.DEBUG, LogLevelEnum.TRACE, LogLevelEnum.VERBOSE }; - public LogLevelEnum NextLevel() { - cur++; - if (cur > 6) { - cur = 0; - } - return levels[cur]; - } - - private void IdList_LayoutUpdated(object sender, EventArgs e) { - Placement(); - } - - async private void CollectLogFileClick(object sender, RoutedEventArgs e) { - await CollectLogFiles(); - } - async private Task CollectLogFiles() { - MonitorServiceStatusEvent resp = await monitorClient.CaptureLogsAsync(); - if (resp != null) { - logger.Info("response: {0}", resp.Message); - } else { - ShowError("Error Collecting Feedback", "An error occurred while trying to gather feedback. Is the monitor service running?"); - } - } - - /// - /// Show the blurb as a growler notification - /// - /// The message to show - /// The url or action name to execute - public async Task ShowBlurbAsync(string message, string url, string level = "error") { - RedBlurb.Visibility = Visibility.Collapsed; - InfoBlurb.Visibility = Visibility.Collapsed; - if (level == "error") { - RedBlurb.Visibility = Visibility.Visible; - } else { - InfoBlurb.Visibility = Visibility.Visible; - } - Blurb.Content = message; - _blurbUrl = url; - BlurbArea.Visibility = Visibility.Visible; - BlurbArea.Opacity = 0; - BlurbArea.Margin = new Thickness(0, 0, 0, 0); - DoubleAnimation animation = new DoubleAnimation(1, TimeSpan.FromSeconds(.3)); - ThicknessAnimation animateThick = new ThicknessAnimation(new Thickness(15, 0, 15, 15), TimeSpan.FromSeconds(.3)); - BlurbArea.BeginAnimation(Grid.OpacityProperty, animation); - BlurbArea.BeginAnimation(Grid.MarginProperty, animateThick); - await Task.Delay(5000); - HideBlurb(); - } - - /// - /// Execute the hide operation wihout an action from the growler - /// - /// The object that was clicked - /// The click event - private void DoHideBlurb(object sender, MouseButtonEventArgs e) { - HideBlurb(); - } - - /// - /// Hide the blurb area - /// - private void HideBlurb() { - DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(.3)); - ThicknessAnimation animateThick = new ThicknessAnimation(new Thickness(0, 0, 0, 0), TimeSpan.FromSeconds(.3)); - animation.Completed += HideComplete; - BlurbArea.BeginAnimation(Grid.OpacityProperty, animation); - BlurbArea.BeginAnimation(Grid.MarginProperty, animateThick); - } - - /// - /// Hide the blurb area after the animation fades out - /// - /// The animation object - /// The completion event - private void HideComplete(object sender, EventArgs e) { - BlurbArea.Visibility = Visibility.Collapsed; - } - - /// - /// Execute a predefined action or url when the pop up is clicked - /// - /// The object that was clicked - /// The click event - private void BlurbAction(object sender, MouseButtonEventArgs e) { - if (_blurbUrl.Length > 0) { - // So this simply execute a url but you could do like if (_blurbUrl=="DoSomethingNifty") CallNifyFunction(); - if (_blurbUrl == this.RECOVER) { - this.ShowMFA(IdentityMenu.Identity, 4); - } else { - Process.Start(new ProcessStartInfo(_blurbUrl) { UseShellExecute = true }); - } - HideBlurb(); - } else { - HideBlurb(); - } - } - - private void ShowAuthenticate(ZitiIdentity identity) { - MFAAuthenticate(identity); - } - - private void ShowRecovery(ZitiIdentity identity) { - ShowMFARecoveryCodes(identity); - } - - - - - - private ICommand someCommand; - public ICommand SomeCommand { - get { - return someCommand - ?? (someCommand = new ActionCommand(() => { - if (DebugForm.Visibility == Visibility.Hidden) { - DebugForm.Visibility = Visibility.Visible; - } else { - DebugForm.Visibility = Visibility.Hidden; - } - })); - } - set { - someCommand = value; - } - } - - private void DoLoading(bool isComplete) { - if (isComplete) HideLoad(); - else ShowLoad("Loading", "Please Wait."); - } - - private void Label_MouseDoubleClick_1(object sender, MouseButtonEventArgs e) { - ShowToast("here's a toast all rightddd..."); - } - } - - public class ActionCommand : ICommand { - private readonly Action _action; - - public ActionCommand(Action action) { - _action = action; - } - - public void Execute(object parameter) { - _action(); - } - - public bool CanExecute(object parameter) { - return true; - } -#pragma warning disable CS0067 //The event 'ActionCommand.CanExecuteChanged' is never used - public event EventHandler CanExecuteChanged; -#pragma warning restore CS0067 //The event 'ActionCommand.CanExecuteChanged' is never used - } -} \ No newline at end of file +/* + Copyright NetFoundry Inc. + + 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using System.IO; +using System.ServiceProcess; +using System.Linq; +using System.Diagnostics; +using System.Windows.Controls; +using System.Drawing; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media.Animation; +using System.Web; +using Microsoft.Toolkit.Uwp.Notifications; + +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.DataStructures; +using ZitiDesktopEdge.ServiceClient; +using ZitiDesktopEdge.Utility; + +using NLog; +using NLog.Config; +using NLog.Targets; +using Microsoft.Win32; + +using Ziti.Desktop.Edge.Models; + +namespace ZitiDesktopEdge { + + public partial class MainWindow : Window { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + public string RECOVER = "RECOVER"; + public System.Windows.Forms.NotifyIcon notifyIcon; + public string Position = "Bottom"; + private DateTime _startDate; + private System.Windows.Forms.Timer _tunnelUptimeTimer; + private DataClient serviceClient = null; + MonitorClient monitorClient = null; + private bool _isAttached = true; + private bool _isServiceInError = false; + private int _right = 75; + private int _left = 75; + private int _top = 30; + private int defaultHeight = 540; + public int NotificationsShownCount = 0; + private double _maxHeight = 800d; + public string CurrentIcon = "white"; + private string[] suffixes = { "Bps", "kBps", "mBps", "gBps", "tBps", "pBps" }; + private string _blurbUrl = ""; + + private DateTime NextNotificationTime; + private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + + static System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); + + public static string ThisAssemblyName; + public static string ExecutionDirectory; + public static string ExpectedLogPathRoot; + public static string ExpectedLogPathUI; + public static string ExpectedLogPathServices; + + private static ZDEWViewState state; + static MainWindow() { + asm = System.Reflection.Assembly.GetExecutingAssembly(); + ThisAssemblyName = asm.GetName().Name; + state = (ZDEWViewState)Application.Current.Properties["ZDEWViewState"]; +#if DEBUG + ExecutionDirectory = @"C:\Program Files (x86)\NetFoundry Inc\Ziti Desktop Edge"; +#else + ExecutionDirectory = Path.GetDirectoryName(asm.Location); +#endif + ExpectedLogPathRoot = Path.Combine(ExecutionDirectory, "logs"); + ExpectedLogPathUI = Path.Combine(ExpectedLogPathRoot, "UI", $"{ThisAssemblyName}.log"); + ExpectedLogPathServices = Path.Combine(ExpectedLogPathRoot, "service", $"ziti-tunneler.log"); + } + + async private void IdentityMenu_OnMessage(string message) { + await ShowBlurbAsync(message, ""); + } + + private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { + LoadIdentities(true); + } + + private List identities { + get { + return (List)Application.Current.Properties["Identities"]; + } + } + + /// + /// The MFA Toggle was toggled + /// + /// True if the toggle was on + private async void MFAToggled(bool isOn) { + if (isOn) { + ShowLoad("Generating MFA", "MFA Setup Commencing, please wait"); + + await serviceClient.EnableMFA(this.IdentityMenu.Identity.Identifier); + } else { + this.ShowMFA(IdentityMenu.Identity, 3); + } + + HideLoad(); + } + + /// + /// When a Service Client is ready to setup the MFA Authorization + /// + /// The service client + /// The MFA Event + private void ServiceClient_OnMfaEvent(object sender, MfaEvent mfa) { + HideLoad(); + this.Dispatcher.Invoke(async () => { + if (mfa.Action == "enrollment_challenge") { + string url = HttpUtility.UrlDecode(mfa.ProvisioningUrl); + string secret = HttpUtility.ParseQueryString(url)["secret"]; + this.IdentityMenu.Identity.RecoveryCodes = mfa?.RecoveryCodes?.ToArray(); + SetupMFA(this.IdentityMenu.Identity, url, secret); + } else if (mfa.Action == "auth_challenge") { + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == mfa.Identifier) { + identities[i].WasNotified = false; + identities[i].WasFullNotified = false; + identities[i].IsMFANeeded = true; + identities[i].IsTimingOut = false; + break; + } + } + } else if (mfa.Action == "enrollment_verification") { + if (mfa.Successful) { + var found = identities.Find(id => id.Identifier == mfa.Identifier); + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == mfa.Identifier) { + identities[i].WasNotified = false; + identities[i].WasFullNotified = false; + identities[i].IsMFANeeded = false; + identities[i].IsMFAEnabled = true; + identities[i].IsTimingOut = false; + identities[i].LastUpdatedTime = DateTime.Now; + for (int j = 0; j < identities[i].Services.Count; j++) { + identities[i].Services[j].TimeUpdated = DateTime.Now; + identities[i].Services[j].TimeoutRemaining = identities[i].Services[j].Timeout; + } + found = identities[i]; + found.IsMFAEnabled = true; + break; + } + } + if (this.IdentityMenu.Identity != null && this.IdentityMenu.Identity.Identifier == mfa.Identifier) this.IdentityMenu.Identity = found; + ShowMFARecoveryCodes(found); + } else { + await ShowBlurbAsync("Provided code could not be verified", ""); + } + } else if (mfa.Action == "enrollment_remove") { + if (mfa.Successful) { + var found = identities.Find(id => id.Identifier == mfa.Identifier); + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == mfa.Identifier) { + identities[i].WasNotified = false; + identities[i].WasFullNotified = false; + identities[i].IsMFAEnabled = false; + identities[i].IsMFANeeded = false; + identities[i].LastUpdatedTime = DateTime.Now; + identities[i].IsTimingOut = false; + for (int j = 0; j < identities[i].Services.Count; j++) { + identities[i].Services[j].TimeUpdated = DateTime.Now; + identities[i].Services[j].TimeoutRemaining = 0; + } + found = identities[i]; + break; + } + } + if (this.IdentityMenu.Identity != null && this.IdentityMenu.Identity.Identifier == mfa.Identifier) this.IdentityMenu.Identity = found; + await ShowBlurbAsync("MFA Disabled, Service Access Can Be Limited", ""); + } else { + await ShowBlurbAsync("MFA Removal Failed", ""); + } + } else if (mfa.Action == "mfa_auth_status") { + var found = identities.Find(id => id.Identifier == mfa.Identifier); + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == mfa.Identifier) { + identities[i].WasNotified = false; + identities[i].WasFullNotified = false; + identities[i].IsTimingOut = false; + identities[i].IsMFANeeded = !mfa.Successful; + identities[i].LastUpdatedTime = DateTime.Now; + for (int j = 0; j < identities[i].Services.Count; j++) { + identities[i].Services[j].TimeUpdated = DateTime.Now; + identities[i].Services[j].TimeoutRemaining = identities[i].Services[j].Timeout; + } + found = identities[i]; + break; + } + } + if (this.IdentityMenu.Identity != null && this.IdentityMenu.Identity.Identifier == mfa.Identifier) this.IdentityMenu.Identity = found; + // ShowBlurb("mfa authenticated: " + mfa.Successful, ""); + } else { + await ShowBlurbAsync("Unexpected error when processing MFA", ""); + logger.Error("unexpected action: " + mfa.Action); + } + + LoadIdentities(true); + }); + } + + /// + /// Show the MFA Setup Modal + /// + /// The Ziti Identity to Setup + public void SetupMFA(ZitiIdentity identity, string url, string secret) { + MFASetup.Opacity = 0; + MFASetup.Visibility = Visibility.Visible; + MFASetup.Margin = new Thickness(0, 0, 0, 0); + MFASetup.BeginAnimation(Grid.OpacityProperty, new DoubleAnimation(1, TimeSpan.FromSeconds(.3))); + MFASetup.BeginAnimation(Grid.MarginProperty, new ThicknessAnimation(new Thickness(30, 30, 30, 30), TimeSpan.FromSeconds(.3))); + MFASetup.ShowSetup(identity, url, secret); + ShowModal(); + } + + /// + /// Show the MFA Authentication Screen when it is time to authenticate + /// + /// The Ziti Identity to Authenticate + public void MFAAuthenticate(ZitiIdentity identity) { + this.ShowMFA(identity, 1); + } + + /// + /// Show MFA for the identity and set the type of screen to show + /// + /// The Identity that is currently active + /// The type of screen to show - 1 Setup, 2 Authenticate, 3 Remove MFA, 4 Regenerate Codes + private void ShowMFA(ZitiIdentity identity, int type) { + MFASetup.Opacity = 0; + MFASetup.Visibility = Visibility.Visible; + MFASetup.Margin = new Thickness(0, 0, 0, 0); + + DoubleAnimation animatin = new DoubleAnimation(1, TimeSpan.FromSeconds(.3)); + animatin.Completed += Animatin_Completed; + MFASetup.BeginAnimation(Grid.OpacityProperty, animatin); + MFASetup.BeginAnimation(Grid.MarginProperty, new ThicknessAnimation(new Thickness(30, 30, 30, 30), TimeSpan.FromSeconds(.3))); + + MFASetup.ShowMFA(identity, type); + + ShowModal(); + } + + private void Animatin_Completed(object sender, EventArgs e) { + MFASetup.AuthCode.Focusable = true; + MFASetup.AuthCode.Focus(); + } + + /// + /// Show the MFA Recovery Codes + /// + /// The Ziti Identity to Authenticate + async public void ShowMFARecoveryCodes(ZitiIdentity identity) { + if (identity.IsMFAEnabled) { + if (identity.IsMFAEnabled && identity.RecoveryCodes != null) { + MFASetup.Opacity = 0; + MFASetup.Visibility = Visibility.Visible; + MFASetup.Margin = new Thickness(0, 0, 0, 0); + MFASetup.BeginAnimation(Grid.OpacityProperty, new DoubleAnimation(1, TimeSpan.FromSeconds(.3))); + MFASetup.BeginAnimation(Grid.MarginProperty, new ThicknessAnimation(new Thickness(30, 30, 30, 30), TimeSpan.FromSeconds(.3))); + + MFASetup.ShowRecovery(identity.RecoveryCodes, identity); + + ShowModal(); + } else { + this.ShowMFA(IdentityMenu.Identity, 2); + } + } else { + await ShowBlurbAsync("MFA is not setup on this Identity", ""); + } + } + + /// + /// Show the modal, aniimating opacity + /// + private void ShowModal() { + ModalBg.Visibility = Visibility.Visible; + ModalBg.Opacity = 0; + DoubleAnimation animation = new DoubleAnimation(.8, TimeSpan.FromSeconds(.3)); + ModalBg.BeginAnimation(Grid.OpacityProperty, animation); + } + + /// + /// Close the various MFA windows + /// + /// The close button + /// The event arguments + private void CloseComplete(object sender, EventArgs e) { + MFASetup.Visibility = Visibility.Collapsed; + } + + /// + /// Hide the modal animating the opacity + /// + private void HideModal() { + DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(.3)); + animation.Completed += ModalHideComplete; + ModalBg.BeginAnimation(Grid.OpacityProperty, animation); + } + + /// + /// When the animation completes, set the visibility to avoid UI object conflicts + /// + /// The animation + /// The event + private void ModalHideComplete(object sender, EventArgs e) { + ModalBg.Visibility = Visibility.Collapsed; + } + + /// + /// Close the MFA Screen with animation + /// + private void DoClose(bool isComplete) { + DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(.3)); + ThicknessAnimation animateThick = new ThicknessAnimation(new Thickness(0, 0, 0, 0), TimeSpan.FromSeconds(.3)); + animation.Completed += CloseComplete; + MFASetup.BeginAnimation(Grid.OpacityProperty, animation); + MFASetup.BeginAnimation(Grid.MarginProperty, animateThick); + HideModal(); + if (isComplete) { + if (MFASetup.Type == 1) { + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == MFASetup.Identity.Identifier) { + identities[i] = MFASetup.Identity; + identities[i].LastUpdatedTime = DateTime.Now; + } + } + } + } + if (IdentityMenu.IsVisible) { + if (isComplete) { + if (MFASetup.Type == 2) { + ShowRecovery(IdentityMenu.Identity); + } else if (MFASetup.Type == 3) { + } else if (MFASetup.Type == 4) { + ShowRecovery(IdentityMenu.Identity); + } + } + IdentityMenu.UpdateView(); + } + LoadIdentities(true); + } + + private void AddIdentity(ZitiIdentity id) { + semaphoreSlim.Wait(); + if (!identities.Any(i => id.Identifier == i.Identifier)) { + identities.Add(id); + } + semaphoreSlim.Release(); + } + + private System.Windows.Forms.ContextMenu contextMenu; + private System.Windows.Forms.MenuItem contextMenuItem; + private System.ComponentModel.IContainer components; + public MainWindow() { + InitializeComponent(); + NextNotificationTime = DateTime.Now; + SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; + string nlogFile = Path.Combine(ExecutionDirectory, ThisAssemblyName + "-log.config"); + + + ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompat_OnActivated; + + bool byFile = false; + if (File.Exists(nlogFile)) { + LogManager.Configuration = new XmlLoggingConfiguration(nlogFile); + byFile = true; + } else { + var config = new LoggingConfiguration(); + // Targets where to log to: File and Console + var logfile = new FileTarget("logfile") { + FileName = ExpectedLogPathUI, + ArchiveEvery = FileArchivePeriod.Day, + ArchiveNumbering = ArchiveNumberingMode.Rolling, + MaxArchiveFiles = 7, + AutoFlush = true, + Layout = "[${date:format=yyyy-MM-ddTHH:mm:ss.fff}Z] ${level:uppercase=true:padding=5}\t${logger}\t${message}\t${exception:format=tostring}", + }; + var logconsole = new ConsoleTarget("logconsole"); + + // Rules for mapping loggers to targets + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logconsole); + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); + + // Apply config + LogManager.Configuration = config; + } + logger.Info("============================== UI started =============================="); + logger.Info("logger initialized"); + logger.Info(" - version : {0}", asm.GetName().Version.ToString()); + logger.Info(" - using file: {0}", byFile); + logger.Info(" - file: {0}", nlogFile); + logger.Info("========================================================================"); + + App.Current.MainWindow.WindowState = WindowState.Normal; + App.Current.MainWindow.Deactivated += MainWindow_Deactivated; + App.Current.MainWindow.Activated += MainWindow_Activated; + App.Current.Exit += Current_Exit; + App.Current.SessionEnding += Current_SessionEnding; + + + this.components = new System.ComponentModel.Container(); + this.contextMenu = new System.Windows.Forms.ContextMenu(); + this.contextMenuItem = new System.Windows.Forms.MenuItem(); + this.contextMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.contextMenuItem }); + + this.contextMenuItem.Index = 0; + this.contextMenuItem.Text = "&Close UI"; + this.contextMenuItem.Click += new System.EventHandler(this.contextMenuItem_Click); + + + notifyIcon = new System.Windows.Forms.NotifyIcon(); + notifyIcon.Visible = true; + notifyIcon.Click += TargetNotifyIcon_Click; + notifyIcon.Visible = true; + notifyIcon.BalloonTipClosed += NotifyIcon_BalloonTipClosed; + notifyIcon.MouseClick += NotifyIcon_MouseClick; + notifyIcon.ContextMenu = this.contextMenu; + + IdentityMenu.OnDetach += OnDetach; + MainMenu.OnDetach += OnDetach; + + this.MainMenu.MainWindow = this; + this.IdentityMenu.MainWindow = this; + SetNotifyIcon("white"); + + this.PreviewKeyDown += KeyPressed; + MFASetup.OnLoad += MFASetup_OnLoad; + MFASetup.OnError += MFASetup_OnError; + IdentityMenu.OnMessage += IdentityMenu_OnMessage; + } + + async private void MFASetup_OnError(string message) { + await ShowBlurbAsync(message, "", "error"); + } + + private static ToastButton feedbackToastButton = new ToastButton() + .SetContent("Click here to collect logs") + .AddArgument("action", "feedback"); + + private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e) { + this.Dispatcher.Invoke(() => { + if (e.Argument != null && e.Argument.Length > 0) { + string[] items = e.Argument.Split(';'); + if (items.Length > 0) { + string[] values = items[0].Split('='); + if (values.Length == 2) { + string identifier = values[1]; + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == identifier) { + ShowMFA(identities[i], 1); + break; + } + } + } + } + } + + ToastArguments args = ToastArguments.Parse(e.Argument); + string value = null; + if (args.TryGetValue("action", out value)) { + this.Dispatcher.Invoke(() => { + MainMenu.CollectFeedbackLogs(e, null); + }); + } + this.Show(); + this.Activate(); + }); + } + + private void KeyPressed(object sender, KeyEventArgs e) { + if (e.Key == Key.Escape) { + if (IdentityMenu.Visibility == Visibility.Visible) IdentityMenu.Visibility = Visibility.Collapsed; + else if (MainMenu.Visibility == Visibility.Visible) MainMenu.Visibility = Visibility.Collapsed; + } + } + + private void MFASetup_OnLoad(bool isComplete, string title, string message) { + if (isComplete) HideLoad(); + else ShowLoad(title, message); + } + + private void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e) { + if (notifyIcon != null) { + notifyIcon.Visible = false; + notifyIcon.Icon.Dispose(); + notifyIcon.Dispose(); + notifyIcon = null; + } + Application.Current.Shutdown(); + } + + private void Current_Exit(object sender, ExitEventArgs e) { + if (notifyIcon != null) { + notifyIcon.Visible = false; + if (notifyIcon.Icon != null) { + notifyIcon.Icon.Dispose(); + } + notifyIcon.Dispose(); + notifyIcon = null; + } + } + + private void contextMenuItem_Click(object Sender, EventArgs e) { + Application.Current.Shutdown(); + } + + private void NotifyIcon_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) { + if (e.Button == System.Windows.Forms.MouseButtons.Left) { + System.Windows.Forms.MouseEventArgs mea = (System.Windows.Forms.MouseEventArgs)e; + this.Show(); + this.Activate(); + //Do the awesome left clickness + } else if (e.Button == System.Windows.Forms.MouseButtons.Right) { + //Do the wickedy right clickness + } else { + //Some other button from the enum :) + } + } + + private void NotifyIcon_BalloonTipClosed(object sender, EventArgs e) { + var thisIcon = (System.Windows.Forms.NotifyIcon)sender; + thisIcon.Visible = false; + thisIcon.Dispose(); + } + + private void Window_MouseDown(object sender, MouseButtonEventArgs e) { + OnDetach(e); + } + + private void OnDetach(MouseButtonEventArgs e) { + if (e.ChangedButton == MouseButton.Left) { + _isAttached = false; + IdentityMenu.Arrow.Visibility = Visibility.Collapsed; + Arrow.Visibility = Visibility.Collapsed; + MainMenu.Detach(); + this.DragMove(); + } + } + + private void MainWindow_Activated(object sender, EventArgs e) { + Placement(); + this.Show(); + this.Visibility = Visibility.Visible; + this.Opacity = 1; + } + + private void MainWindow_Deactivated(object sender, EventArgs e) { + if (this._isAttached) { + this.Visibility = Visibility.Hidden; + } + } + + private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { + if (notifyIcon != null) { + notifyIcon.Visible = false; + notifyIcon.Icon.Dispose(); + notifyIcon.Dispose(); + notifyIcon = null; + } + Application.Current.Shutdown(); + } + + private void SetCantDisplay(string title, string detailMessage, Visibility closeButtonVisibility) { + this.Dispatcher.Invoke(() => { + NoServiceView.Visibility = Visibility.Visible; + CloseErrorButton.IsEnabled = true; + CloseErrorButton.Visibility = closeButtonVisibility; + ErrorMsg.Content = title; + ErrorMsgDetail.Content = detailMessage; + SetNotifyIcon("red"); + _isServiceInError = true; + UpdateServiceView(); + }); + } + + private void TargetNotifyIcon_Click(object sender, EventArgs e) { + this.Show(); + this.Activate(); + Application.Current.MainWindow.Activate(); + } + + private void UpdateServiceView() { + if (_isServiceInError) { + AddIdAreaButton.Opacity = 0.1; + AddIdAreaButton.IsEnabled = false; + AddIdButton.Opacity = 0.1; + AddIdButton.IsEnabled = false; + ConnectButton.Opacity = 0.1; + StatArea.Opacity = 0.1; + } else { + AddIdAreaButton.Opacity = 1.0; + AddIdAreaButton.IsEnabled = true; + AddIdButton.Opacity = 1.0; + AddIdButton.IsEnabled = true; + StatArea.Opacity = 1.0; + ConnectButton.Opacity = 1.0; + } + TunnelConnected(!_isServiceInError); + } + + private void App_ReceiveString(string obj) { + Console.WriteLine(obj); + this.Show(); + this.Activate(); + } + + async private void MainWindow_Loaded(object sender, RoutedEventArgs e) { + + Window window = Window.GetWindow(App.Current.MainWindow); + ZitiDesktopEdge.App app = (ZitiDesktopEdge.App)App.Current; + app.ReceiveString += App_ReceiveString; + + // add a new service client + serviceClient = new DataClient("ui"); + serviceClient.OnClientConnected += ServiceClient_OnClientConnected; + serviceClient.OnClientDisconnected += ServiceClient_OnClientDisconnected; + serviceClient.OnIdentityEvent += ServiceClient_OnIdentityEvent; + serviceClient.OnMetricsEvent += ServiceClient_OnMetricsEvent; + serviceClient.OnServiceEvent += ServiceClient_OnServiceEvent; + serviceClient.OnTunnelStatusEvent += ServiceClient_OnTunnelStatusEvent; + serviceClient.OnMfaEvent += ServiceClient_OnMfaEvent; + serviceClient.OnLogLevelEvent += ServiceClient_OnLogLevelEvent; + serviceClient.OnBulkServiceEvent += ServiceClient_OnBulkServiceEvent; + serviceClient.OnNotificationEvent += ServiceClient_OnNotificationEvent; + serviceClient.OnControllerEvent += ServiceClient_OnControllerEvent; + Application.Current.Properties.Add("ServiceClient", serviceClient); + + monitorClient = new MonitorClient("ui"); + monitorClient.OnClientConnected += MonitorClient_OnClientConnected; + monitorClient.OnNotificationEvent += MonitorClient_OnInstallationNotificationEvent; + monitorClient.OnServiceStatusEvent += MonitorClient_OnServiceStatusEvent; + monitorClient.OnShutdownEvent += MonitorClient_OnShutdownEvent; + monitorClient.OnCommunicationError += MonitorClient_OnCommunicationError; + monitorClient.OnReconnectFailure += MonitorClient_OnReconnectFailure; + Application.Current.Properties.Add("MonitorClient", monitorClient); + + Application.Current.Properties.Add("Identities", new List()); + MainMenu.OnAttachmentChange += AttachmentChanged; + MainMenu.OnLogLevelChanged += LogLevelChanged; + MainMenu.OnShowBlurb += MainMenu_OnShowBlurb; + IdentityMenu.OnError += IdentityMenu_OnError; + + try { + await serviceClient.ConnectAsync(); + await serviceClient.WaitForConnectionAsync(); + } catch /*ignored for now (Exception ex) */ + { + ShowServiceNotStarted(); + serviceClient.Reconnect(); + } + + try { + await monitorClient.ConnectAsync(); + await monitorClient.WaitForConnectionAsync(); + } catch /*ignored for now (Exception ex) */ + { + monitorClient.Reconnect(); + } + + IdentityMenu.OnForgot += IdentityForgotten; + Placement(); + } + + private void MonitorClient_OnCommunicationError(object sender, Exception e) { + string msg = "Communication Error with monitor?"; + ShowError(msg, e.Message); + } + + private void MainMenu_OnShowBlurb(string message) { + _ = ShowBlurbAsync(message, "", "info"); + } + + private void ServiceClient_OnBulkServiceEvent(object sender, BulkServiceEvent e) { + var found = identities.Find(id => id.Identifier == e.Identifier); + if (found == null) { + logger.Warn($"{e.Action} service event for {e.Identifier} but the provided identity identifier was not found!"); + return; + } else { + if (e.RemovedServices != null) { + foreach (var removed in e.RemovedServices) { + removeService(found, removed); + } + } + if (e.AddedServices != null) { + foreach (var added in e.AddedServices) { + addService(found, added); + } + } + LoadIdentities(true); + this.Dispatcher.Invoke(() => { + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + if (deets.IsVisible) { + deets.UpdateView(); + } + }); + } + } + + private void ServiceClient_OnNotificationEvent(object sender, NotificationEvent e) { + var displayMFARequired = false; + var displayMFATimout = false; + foreach (var notification in e.Notification) { + var found = identities.Find(id => id.Identifier == notification.Identifier); + if (found == null) { + logger.Warn($"{e.Op} event for {notification.Identifier} but the provided identity identifier was not found!"); + continue; + } else { + found.TimeoutMessage = notification.Message; + found.MaxTimeout = notification.MfaMaximumTimeout; + found.MinTimeout = notification.MfaMinimumTimeout; + + if (notification.MfaMinimumTimeout == 0) { + // display mfa token icon + displayMFARequired = true; + } else { + displayMFATimout = true; + } + + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == found.Identifier) { + identities[i] = found; + break; + } + } + } + } + + // we may need to display mfa icon, based on the timer in UI, remove found.MFAInfo.ShowMFA setting in this function. + // the below function can show mfa icon even after user authenticates successfully, in race conditions + if (displayMFARequired || displayMFATimout) { + this.Dispatcher.Invoke(() => { + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + if (deets.IsVisible) { + deets.UpdateView(); + } + }); + } + LoadIdentities(true); + } + + private void ServiceClient_OnControllerEvent(object sender, ControllerEvent e) { + logger.Debug($"==== ControllerEvent : action:{e.Action} identifier:{e.Identifier}"); + // commenting this block, because when it receives the disconnected events, identities are disabled and + // it is not allowing me to click/perform any operation on the identity + // the color of the title is also too dark, and it is not clearly visible, when the identity is disconnected + /* if (e.Action == "connected") { + var found = identities.Find(i => i.Identifier == e.Identifier); + found.IsConnected = true; + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == found.Identifier) { + identities[i] = found; + break; + } + } + LoadIdentities(true); + } else if (e.Action == "disconnected") { + var found = identities.Find(i => i.Identifier == e.Identifier); + found.IsConnected = false; + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == found.Identifier) { + identities[i] = found; + break; + } + } + LoadIdentities(true); + } */ + } + + + string nextVersionStr = null; + private void MonitorClient_OnReconnectFailure(object sender, object e) { + logger.Trace("OnReconnectFailure triggered"); + if (nextVersionStr == null) { + // check for the current version + nextVersionStr = "checking for update"; + Version nextVersion = GithubAPI.GetVersion(GithubAPI.GetJson(GithubAPI.ProdUrl)); + nextVersionStr = nextVersion.ToString(); + Version currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; //fetch from ziti? + + int compare = currentVersion.CompareTo(nextVersion); + if (compare < 0) { + MainMenu.SetAppUpgradeAvailableText("Upgrade available: " + nextVersionStr); + logger.Info("upgrade is available. Published version: {} is newer than the current version: {}", nextVersion, currentVersion); + //UpgradeAvailable(); + } else if (compare > 0) { + logger.Info("the version installed: {0} is newer than the released version: {1}", currentVersion, nextVersion); + MainMenu.SetAppIsNewer("This version is newer than the latest: " + nextVersionStr); + } else { + logger.Info("Current version installed: {0} is the same as the latest released version {1}", currentVersion, nextVersion); + MainMenu.SetAppUpgradeAvailableText(""); + } + } + } + + private void MonitorClient_OnShutdownEvent(object sender, StatusEvent e) { + logger.Info("The monitor has indicated the application should shut down."); + this.Dispatcher.Invoke(() => { + Application.Current.Shutdown(); + }); + } + + private void MonitorClient_OnServiceStatusEvent(object sender, MonitorServiceStatusEvent evt) { + this.Dispatcher.Invoke(() => { + try { + if (evt.Message?.ToLower() == "upgrading") { + logger.Info("The monitor has indicated an upgrade is in progress. Shutting down the UI"); + UpgradeSentinel.StartUpgradeSentinel(); + + App.Current.Exit -= Current_Exit; + logger.Info("Removed Current_Exit handler"); + notifyIcon.Visible = false; + notifyIcon.Icon.Dispose(); + notifyIcon.Dispose(); + Application.Current.Shutdown(); + return; + } + SetAutomaticUpdateEnabled(evt.AutomaticUpgradeDisabled, evt.AutomaticUpgradeURL); + if (evt.Code != 0) { + logger.Error("CODE: " + evt.Code); + if (MainMenu.ShowUnexpectedFailure) { + ShowToast("The data channel has stopped unexpectedly", $"If this keeps happening please collect logs and report the issue.", feedbackToastButton); + } + } + MainMenu.ShowUpdateAvailable(); + logger.Debug("MonitorClient_OnServiceStatusEvent: {0}", evt.Status); + Application.Current.Properties["ReleaseStream"] = evt.ReleaseStream; + + ServiceControllerStatus status = (ServiceControllerStatus)Enum.Parse(typeof(ServiceControllerStatus), evt.Status); + + switch (status) { + case ServiceControllerStatus.Running: + logger.Info("Service is started"); + break; + case ServiceControllerStatus.Stopped: + logger.Info("Service is stopped"); + ShowServiceNotStarted(); + break; + case ServiceControllerStatus.StopPending: + logger.Info("Service is stopping..."); + + this.Dispatcher.Invoke(async () => { + SetCantDisplay("The Service is Stopping", "Please wait while the service stops", Visibility.Visible); + await WaitForServiceToStop(DateTime.Now + TimeSpan.FromSeconds(30)); + }); + break; + case ServiceControllerStatus.StartPending: + logger.Info("Service is starting..."); + break; + case ServiceControllerStatus.PausePending: + logger.Warn("UNEXPECTED STATUS: PausePending"); + break; + case ServiceControllerStatus.Paused: + logger.Warn("UNEXPECTED STATUS: Paused"); + break; + default: + logger.Warn("UNEXPECTED STATUS: {0}", evt.Status); + break; + } + } catch (Exception ex) { + logger.Warn(ex, "unexpected exception in MonitorClient_OnServiceStatusEvent? {0}", ex.Message); + } + }); + } + + public void SetAutomaticUpdateEnabled(string enabled, string url) { + this.Dispatcher.Invoke(() => { + state.AutomaticUpdatesDisabled = bool.Parse(enabled); + state.AutomaticUpdateURL = url; + }); + } + + private void MonitorClient_OnInstallationNotificationEvent(object sender, InstallationNotificationEvent evt) { + this.Dispatcher.Invoke(() => { + logger.Debug("MonitorClient_OnInstallationNotificationEvent: {0}", evt.Message); + switch (evt.Message?.ToLower()) { + case "installationupdate": + logger.Debug("Installation Update is available - {0}", evt.ZDEVersion); + var remaining = evt.InstallTime - DateTime.Now; + + state.PendingUpdate.Version = evt.ZDEVersion; + state.PendingUpdate.InstallTime = evt.InstallTime; + state.UpdateAvailable = true; + SetAutomaticUpdateEnabled(evt.AutomaticUpgradeDisabled, evt.AutomaticUpgradeURL); + MainMenu.ShowUpdateAvailable(); + AlertCanvas.Visibility = Visibility.Visible; + + if (isToastEnabled()) { + if (!state.AutomaticUpdatesDisabled) { + if (remaining.TotalSeconds < 60) { + //this is an immediate update - show a different message + ShowToast("Ziti Desktop Edge will initiate auto installation in the next minute!"); + } else { + if (DateTime.Now > NextNotificationTime) { + ShowToast($"Update {evt.ZDEVersion} is available for Ziti Desktop Edge and will be automatically installed by " + evt.InstallTime); + NextNotificationTime = DateTime.Now + evt.NotificationDuration; + } else { + logger.Debug("Skipping notification. Time until next notification {} seconds which is at {}", (int)((NextNotificationTime - DateTime.Now).TotalSeconds), NextNotificationTime); + } + } + } else { + ShowToast("New version available", $"Version {evt.ZDEVersion} is available for Ziti Desktop Edge", null); + } + SetNotifyIcon(""); + // display a tag in UI and a button for the update software + } + break; + case "configuration changed": + break; + default: + logger.Debug("unexpected event type?"); + break; + } + }); + } + + private bool isToastEnabled() { + bool result; + //only show notifications once if automatic updates are disabled + if (NotificationsShownCount == 0) { + result = true; //regardless - if never notified, always return true + } else { + result = !state.AutomaticUpdatesDisabled; + } + return result; + } + + private void ShowToast(string header, string message, ToastButton button) { + try { + logger.Debug("showing toast: {} {}", header, message); + var builder = new ToastContentBuilder() + .AddArgument("notbutton", "click") + .AddText(header) + .AddText(message); + if (button != null) { + builder.AddButton(button); + } + builder.Show(); + NotificationsShownCount++; + } catch { + logger.Warn("couldn't show toast: {} {}", header, message); + } + } + + + private void ShowToast(string message) { + ShowToast("Important Notice", message, null); + } + + async private Task WaitForServiceToStop(DateTime until) { + //continually poll for the service to stop. If it is stuck - ask the user if they want to try to force + //close the service + while (DateTime.Now < until) { + await Task.Delay(250); + MonitorServiceStatusEvent resp = await monitorClient.StatusAsync(); + if (resp.IsStopped()) { + // good - that's what we are waiting for... + return; + } else { + // bad - not stopped yet... + logger.Debug("Waiting for service to stop... Still not stopped yet. Status: {0}", resp.Status); + } + } + // real bad - means it's stuck probably. Ask the user if they want to try to force it... + logger.Warn("Waiting for service to stop... Service did not reach stopped state in the expected amount of time."); + SetCantDisplay("The Service Appears Stuck", "Would you like to try to force close the service?", Visibility.Visible); + CloseErrorButton.Content = "Force Quit"; + CloseErrorButton.Click -= CloseError; + CloseErrorButton.Click += ForceQuitButtonClick; + } + + async private void ForceQuitButtonClick(object sender, RoutedEventArgs e) { + MonitorServiceStatusEvent status = await monitorClient.ForceTerminateAsync(); + if (status.IsStopped()) { + //good + CloseErrorButton.Click += CloseError; //reset the close button... + CloseErrorButton.Click -= ForceQuitButtonClick; + } else { + //bad... + SetCantDisplay("The Service Is Still Running", "Current status is: " + status.Status, Visibility.Visible); + } + } + + async private void StartZitiService(object sender, RoutedEventArgs e) { + try { + ShowLoad("Starting", "Starting the data service"); + logger.Info("StartZitiService"); + var r = await monitorClient.StartServiceAsync(); + if (r.Code != 0) { + logger.Debug("ERROR: {0} : {1}", r.Message, r.Error); + } else { + logger.Info("Service started!"); + CloseErrorButton.Click -= StartZitiService; + CloseError(null, null); + } + } catch (MonitorServiceException me) { + logger.Warn("the monitor service appears offline. {0}", me); + CloseErrorButton.IsEnabled = true; + HideLoad(); + ShowError("Error Starting Service", "The monitor service is offline"); + } catch (Exception ex) { + logger.Error(ex, "UNEXPECTED ERROR!"); + CloseErrorButton.IsEnabled = true; + HideLoad(); + ShowError("Unexpected Error", "Code 2:" + ex.Message); + } + CloseErrorButton.IsEnabled = true; + // HideLoad(); + } + + private void ShowServiceNotStarted() { + TunnelConnected(false); + LoadIdentities(true); + } + + private void MonitorClient_OnClientConnected(object sender, object e) { + logger.Debug("MonitorClient_OnClientConnected"); + MainMenu.SetAppUpgradeAvailableText(""); + } + + async private Task LogLevelChanged(string level) { + int logsSet = 0; + try { + await serviceClient.SetLogLevelAsync(level); + logsSet++; + await monitorClient.SetLogLevelAsync(level); + logsSet++; + Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(level); + return true; + } catch (Exception ex) { + logger.Error(ex, "Unexpected error. logsSet: {0}", logsSet); + if (logsSet > 1) { + await ShowBlurbAsync("Unexpected error setting logs?", ""); + } else if (logsSet > 0) { + await ShowBlurbAsync("Failed to set monitor client log level", ""); + } else { + await ShowBlurbAsync("Failed to set log levels", ""); + } + } + return false; + } + + private void IdentityMenu_OnError(string message) { + ShowError("Identity Error", message); + } + + private void ServiceClient_OnClientConnected(object sender, object e) { + this.Dispatcher.Invoke(() => { + MainMenu.Connected(); + NoServiceView.Visibility = Visibility.Collapsed; + _isServiceInError = false; + UpdateServiceView(); + SetNotifyIcon("white"); + LoadIdentities(true); + }); + } + + private void ServiceClient_OnClientDisconnected(object sender, object e) { + this.Dispatcher.Invoke(() => { + IdentityMenu.Visibility = Visibility.Collapsed; + MFASetup.Visibility = Visibility.Collapsed; + HideModal(); + MainMenu.Disconnected(); + for (int i = 0; i < IdList.Children.Count; i++) { + IdentityItem item = (IdentityItem)IdList.Children[i]; + item.StopTimers(); + } + IdList.Children.Clear(); + if (e != null) { + logger.Debug(e.ToString()); + } + //SetCantDisplay("Start the Ziti Tunnel Service to continue"); + SetNotifyIcon("red"); + ShowServiceNotStarted(); + }); + } + + /// + /// If an identity gets added late, execute this. + /// + /// Do not update services for identity events + /// + /// The sending service + /// The identity event + private void ServiceClient_OnIdentityEvent(object sender, IdentityEvent e) { + if (e == null) return; + + ZitiIdentity zid = ZitiIdentity.FromClient(e.Id); + logger.Debug($"==== IdentityEvent : action:{e.Action} identifer:{e.Id.Identifier} name:{e.Id.Name} "); + + this.Dispatcher.Invoke(async () => { + if (e.Action == "added") { + var found = identities.Find(i => i.Identifier == e.Id.Identifier); + if (found == null) { + AddIdentity(zid); + LoadIdentities(true); + } else { + // means we likely are getting an update for some reason. compare the identities and use the latest info + if (zid.Name != null && zid.Name.Length > 0) found.Name = zid.Name; + if (zid.ControllerUrl != null && zid.ControllerUrl.Length > 0) found.ControllerUrl = zid.ControllerUrl; + if (zid.ContollerVersion != null && zid.ContollerVersion.Length > 0) found.ContollerVersion = zid.ContollerVersion; + found.IsEnabled = zid.IsEnabled; + found.IsMFAEnabled = e.Id.MfaEnabled; + found.IsConnected = true; + found.NeedsExtAuth = e.Id.NeedsExtAuth; + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == found.Identifier) { + identities[i] = found; + break; + } + } + LoadIdentities(true); + } + } else if (e.Action == "updated") { + //this indicates that all updates have been sent to the UI... wait for 2 seconds then trigger any ui updates needed + await Task.Delay(2000); + LoadIdentities(true); + } else if (e.Action == "connected") { + var found = identities.Find(i => i.Identifier == e.Id.Identifier); + found.IsConnected = true; + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == found.Identifier) { + identities[i] = found; + break; + } + } + LoadIdentities(true); + } else if (e.Action == "disconnected") { + var found = identities.Find(i => i.Identifier == e.Id.Identifier); + found.IsConnected = false; + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == found.Identifier) { + identities[i] = found; + break; + } + } + LoadIdentities(true); + } else if (e.Action == "needs_ext_login") { + logger.Debug("needs_ext_login action received"); //handled through identity event at the moment (forever?) + } else { + logger.Warn("unexpected action received: {}", e.Action); + IdentityForgotten(ZitiIdentity.FromClient(e.Id)); + } + }); + logger.Debug($"IDENTITY EVENT. Action: {e.Action} identifier: {zid.Identifier}"); + } + + private void ServiceClient_OnMetricsEvent(object sender, List ids) { + if (ids != null) { + long totalUp = 0; + long totalDown = 0; + foreach (var id in ids) { + //logger.Debug($"==== MetricsEvent : id {id.Name} down: {id.Metrics.Down} up:{id.Metrics.Up}"); + if (id?.Metrics != null) { + totalDown += id.Metrics.Down; + totalUp += id.Metrics.Up; + } + } + this.Dispatcher.Invoke(() => { + SetSpeed(totalUp, UploadSpeed, UploadSpeedLabel); + SetSpeed(totalDown, DownloadSpeed, DownloadSpeedLabel); + }); + } + } + + public void SetSpeed(decimal bytes, Label speed, Label speedLabel) { + int counter = 0; + while (Math.Round(bytes / 1024) >= 1) { + bytes = bytes / 1024; + counter++; + } + speed.Content = bytes.ToString("0.0"); + speedLabel.Content = suffixes[counter]; + } + + private void ServiceClient_OnServiceEvent(object sender, ServiceEvent e) { + if (e == null) return; + + logger.Debug($"==== ServiceEvent : action:{e.Action} identifier:{e.Identifier} name:{e.Service.Name} "); + var found = identities.Find(id => id.Identifier == e.Identifier); + if (found == null) { + logger.Debug($"{e.Action} service event for {e.Service.Name} but the provided identity identifier {e.Identifier} is not found!"); + return; + } + + if (e.Action == "added") { + addService(found, e.Service); + } else { + removeService(found, e.Service); + } + LoadIdentities(true); + this.Dispatcher.Invoke(() => { + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + if (deets.IsVisible) { + deets.UpdateView(); + } + }); + } + + private void addService(ZitiIdentity found, Service added) { + ZitiService zs = new ZitiService(added); + var svc = found.Services.Find(s => s.Name == zs.Name); + if (svc == null) { + logger.Debug("Service Added: " + zs.Name); + found.Services.Add(zs); + if (zs.HasFailingPostureCheck()) { + found.HasServiceFailingPostureCheck = true; + if (zs.PostureChecks.Any(p => !p.IsPassing && p.QueryType == "MFA")) { + found.IsMFANeeded = true; + } + } + } else { + logger.Debug("the service named " + zs.Name + " is already accounted for on this identity."); + } + } + + private void removeService(ZitiIdentity found, Service removed) { + logger.Debug("removing the service named: {0}", removed.Name); + found.Services.RemoveAll(s => s.Name == removed.Name); + } + + private void ServiceClient_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { + if (e == null) return; //just skip it for now... + logger.Debug($"==== TunnelStatusEvent: "); + Application.Current.Properties.Remove("CurrentTunnelStatus"); + Application.Current.Properties.Add("CurrentTunnelStatus", e.Status); + e.Status.Dump(Console.Out); + this.Dispatcher.Invoke(() => { + /*if (e.ApiVersion != DataClient.EXPECTED_API_VERSION) { + SetCantDisplay("Version mismatch!", "The version of the Service is not compatible", Visibility.Visible); + return; + }*/ + this.MainMenu.LogLevel = e.Status.LogLevel; + Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(e.Status.LogLevel); + InitializeTimer((int)e.Status.Duration); + LoadStatusFromService(e.Status); + LoadIdentities(true); + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + if (deets.IsVisible) { + deets.UpdateView(); + } + }); + } + + private void ServiceClient_OnLogLevelEvent(object sender, LogLevelEvent e) { + if (e.LogLevel != null) { + SetLogLevel_monitor(e.LogLevel); + this.Dispatcher.Invoke(() => { + this.MainMenu.LogLevel = e.LogLevel; + Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(e.LogLevel); + }); + } + } + + async private void SetLogLevel_monitor(string loglevel) { + await monitorClient.SetLogLevelAsync(loglevel); + } + + private void IdentityForgotten(ZitiIdentity forgotten) { + ZitiIdentity idToRemove = null; + foreach (var id in identities) { + if (id.Identifier == forgotten.Identifier) { + idToRemove = id; + break; + } + } + identities.Remove(idToRemove); + LoadIdentities(false); + } + + private void AttachmentChanged(bool attached) { + _isAttached = attached; + if (!_isAttached) { + SetLocation(); + } + Placement(); + MainMenu.Visibility = Visibility.Collapsed; + } + + private void LoadStatusFromService(TunnelStatus status) { + //clear any identities + this.identities.Clear(); + + if (status != null) { + _isServiceInError = false; + UpdateServiceView(); + NoServiceView.Visibility = Visibility.Collapsed; + SetNotifyIcon("green"); + if (!Application.Current.Properties.Contains("ip")) { + Application.Current.Properties.Add("ip", status?.IpInfo?.Ip); + } else { + Application.Current.Properties["ip"] = status?.IpInfo?.Ip; + } + if (!Application.Current.Properties.Contains("subnet")) { + Application.Current.Properties.Add("subnet", status?.IpInfo?.Subnet); + } else { + Application.Current.Properties["subnet"] = status?.IpInfo?.Subnet; + } + if (!Application.Current.Properties.Contains("mtu")) { + Application.Current.Properties.Add("mtu", status?.IpInfo?.MTU); + } else { + Application.Current.Properties["mtu"] = status?.IpInfo?.MTU; + } + if (!Application.Current.Properties.Contains("dns")) { + Application.Current.Properties.Add("dns", status?.IpInfo?.DNS); + } else { + Application.Current.Properties["dns"] = status?.IpInfo?.DNS; + } + if (!Application.Current.Properties.Contains("dnsenabled")) { + Application.Current.Properties.Add("dnsenabled", status?.AddDns); + } else { + Application.Current.Properties["dnsenabled"] = status?.AddDns; + } + + string key = "ApiPageSize"; + if (!Application.Current.Properties.Contains(key)) { + Application.Current.Properties.Add(key, status?.ApiPageSize); + } else { + Application.Current.Properties[key] = status?.ApiPageSize; + } + + foreach (var id in status.Identities) { + updateViewWithIdentity(id); + } + LoadIdentities(true); + } else { + ShowServiceNotStarted(); + } + } + + private void updateViewWithIdentity(Identity id) { + var zid = ZitiIdentity.FromClient(id); + foreach (var i in identities) { + if (i.Identifier == zid.Identifier) { + identities.Remove(i); + break; + } + } + identities.Add(zid); + } + + private bool IsTimingOut() { + if (identities != null) { + for (int i = 0; i < identities.Count; i++) { + if (identities[i].IsTimingOut) return true; + } + } + return false; + } + + private bool IsTimedOut() { + if (identities != null) { + return identities.Any(i => i.IsTimedOut); + } + return false; + } + + private void SetNotifyIcon(string iconPrefix) { + if (iconPrefix != "") CurrentIcon = iconPrefix; + string icon = "pack://application:,,/Assets/Images/ziti-" + CurrentIcon; + if (state.UpdateAvailable) { + icon += "-update"; + } else { + if (IsTimedOut()) { + icon += "-mfa"; + } else { + if (IsTimingOut()) { + icon += "-timer"; + } + } + } + icon += ".ico"; + var iconUri = new Uri(icon); + Stream iconStream = Application.GetResourceStream(iconUri).Stream; + notifyIcon.Icon = new Icon(iconStream); + + Application.Current.MainWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconUri); + } + + private void LoadIdentities(Boolean repaint) { + this.Dispatcher.Invoke(() => { + for (int i = 0; i < IdList.Children.Count; i++) { + IdentityItem item = (IdentityItem)IdList.Children[i]; + item.StopTimers(); + } + IdList.Children.Clear(); + IdList.Height = 0; + var desktopWorkingArea = SystemParameters.WorkArea; + if (_maxHeight > (desktopWorkingArea.Height - 10)) _maxHeight = desktopWorkingArea.Height - 10; + if (_maxHeight < 100) _maxHeight = 100; + IdList.MaxHeight = _maxHeight - 520; + ZitiIdentity[] ids = identities.OrderBy(i => (i.Name != null) ? i.Name.ToLower() : i.Name).ToArray(); + MainMenu.SetupIdList(ids); + if (ids.Length > 0 && serviceClient.Connected) { + double height = defaultHeight + (ids.Length * 60); + if (height > _maxHeight) height = _maxHeight; + this.Height = height; + IdentityMenu.SetHeight(this.Height - 160); + MainMenu.IdentitiesButton.Visibility = Visibility.Visible; + foreach (var id in ids) { + IdentityItem idItem = new IdentityItem(); + + idItem.ToggleStatus.IsEnabled = id.IsEnabled; + if (id.IsEnabled) idItem.ToggleStatus.Content = "ENABLED"; + else idItem.ToggleStatus.Content = "DISABLED"; + + idItem.Authenticate += IdItem_Authenticate; + idItem.OnStatusChanged += Id_OnStatusChanged; + idItem.Identity = id; + idItem.IdentityChanged += IdItem_IdentityChanged; + + if (repaint) { + idItem.RefreshUI(); + } + + IdList.Children.Add(idItem); + + if (IdentityMenu.Visibility == Visibility.Visible) { + if (id.Identifier == IdentityMenu.Identity.Identifier) IdentityMenu.Identity = id; + } + } + DoubleAnimation animation = new DoubleAnimation((double)(ids.Length * 64), TimeSpan.FromSeconds(.2)); + IdList.BeginAnimation(FrameworkElement.HeightProperty, animation); + IdListScroller.Visibility = Visibility.Visible; + } else { + this.Height = defaultHeight; + MainMenu.IdentitiesButton.Visibility = Visibility.Collapsed; + IdListScroller.Visibility = Visibility.Visible; + + } + AddIdButton.Visibility = Visibility.Visible; + AddIdAreaButton.Visibility = Visibility.Visible; + + Placement(); + SetNotifyIcon(""); + }); + } + + private void IdItem_IdentityChanged(ZitiIdentity identity) { + for (int i = 0; i < identities.Count; i++) { + if (identities[i].Identifier == identity.Identifier) { + identities[i] = identity; + break; + } + } + SetNotifyIcon(""); + } + + private void IdItem_Authenticate(ZitiIdentity identity) { + ShowAuthenticate(identity); + } + + private void Id_OnStatusChanged(bool attached) { + for (int i = 0; i < IdList.Children.Count; i++) { + IdentityItem item = IdList.Children[i] as IdentityItem; + if (item.ToggleSwitch.Enabled) break; + } + } + + private void TunnelConnected(bool isConnected) { + this.Dispatcher.Invoke(() => { + if (isConnected) { + ConnectButton.Visibility = Visibility.Collapsed; + DisconnectButton.Visibility = Visibility.Visible; + MainMenu.Connected(); + HideLoad(); + SetNotifyIcon("green"); + } else { + ConnectButton.Visibility = Visibility.Visible; + DisconnectButton.Visibility = Visibility.Collapsed; + IdentityMenu.Visibility = Visibility.Collapsed; + MainMenu.Visibility = Visibility.Collapsed; + HideBlurb(); + MainMenu.Disconnected(); + DownloadSpeed.Content = "0.0"; + UploadSpeed.Content = "0.0"; + } + }); + } + + private void SetLocation() { + var desktopWorkingArea = SystemParameters.WorkArea; + + var height = MainView.ActualHeight; + IdentityMenu.MainHeight = MainView.ActualHeight; + MainMenu.MainHeight = MainView.ActualHeight; + + Rectangle trayRectangle = WinAPI.GetTrayRectangle(); + if (trayRectangle.Top < 20) { + this.Position = "Top"; + this.Top = desktopWorkingArea.Top + _top; + this.Left = desktopWorkingArea.Right - this.Width - _right; + Arrow.SetValue(Canvas.TopProperty, (double)0); + Arrow.SetValue(Canvas.LeftProperty, (double)185); + MainMenu.Arrow.SetValue(Canvas.TopProperty, (double)0); + MainMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); + IdentityMenu.Arrow.SetValue(Canvas.TopProperty, (double)0); + IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); + } else if (trayRectangle.Left < 20) { + this.Position = "Left"; + this.Left = _left; + this.Top = desktopWorkingArea.Bottom - this.ActualHeight - 75; + Arrow.SetValue(Canvas.TopProperty, height - 200); + Arrow.SetValue(Canvas.LeftProperty, (double)0); + MainMenu.Arrow.SetValue(Canvas.TopProperty, height - 200); + MainMenu.Arrow.SetValue(Canvas.LeftProperty, (double)0); + IdentityMenu.Arrow.SetValue(Canvas.TopProperty, height - 200); + IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, (double)0); + } else if (desktopWorkingArea.Right == (double)trayRectangle.Left) { + this.Position = "Right"; + this.Left = desktopWorkingArea.Right - this.Width - 20; + this.Top = desktopWorkingArea.Bottom - height - 75; + Arrow.SetValue(Canvas.TopProperty, height - 100); + Arrow.SetValue(Canvas.LeftProperty, this.Width - 30); + MainMenu.Arrow.SetValue(Canvas.TopProperty, height - 100); + MainMenu.Arrow.SetValue(Canvas.LeftProperty, this.Width - 30); + IdentityMenu.Arrow.SetValue(Canvas.TopProperty, height - 100); + IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, this.Width - 30); + } else { + this.Position = "Bottom"; + this.Left = desktopWorkingArea.Right - this.Width - 75; + this.Top = desktopWorkingArea.Bottom - height; + Arrow.SetValue(Canvas.TopProperty, height - 35); + Arrow.SetValue(Canvas.LeftProperty, (double)185); + MainMenu.Arrow.SetValue(Canvas.TopProperty, height - 35); + MainMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); + IdentityMenu.Arrow.SetValue(Canvas.TopProperty, height - 35); + IdentityMenu.Arrow.SetValue(Canvas.LeftProperty, (double)185); + } + } + public void Placement() { + if (_isAttached) { + Arrow.Visibility = Visibility.Visible; + IdentityMenu.Arrow.Visibility = Visibility.Visible; + SetLocation(); + } else { + IdentityMenu.Arrow.Visibility = Visibility.Collapsed; + Arrow.Visibility = Visibility.Collapsed; + } + } + + private void OpenIdentity(ZitiIdentity identity) { + IdentityMenu.Identity = identity; + } + + private void ShowMenu(object sender, MouseButtonEventArgs e) { + MainMenu.Visibility = Visibility.Visible; + } + + async private void AddIdentity(object sender, MouseButtonEventArgs e) { + UIModel.HideOnLostFocus = false; + Microsoft.Win32.OpenFileDialog jwtDialog = new Microsoft.Win32.OpenFileDialog(); + UIModel.HideOnLostFocus = true; + jwtDialog.DefaultExt = ".jwt"; + jwtDialog.Filter = "Ziti Identities (*.jwt)|*.jwt"; + + if (jwtDialog.ShowDialog() == true) { + ShowLoad("Adding Identity", "Please wait while the identity is added"); + string fileContent = File.ReadAllText(jwtDialog.FileName); + + try { + Identity createdId = await serviceClient.AddIdentityAsync(System.IO.Path.GetFileName(jwtDialog.FileName), false, fileContent); + + if (createdId != null) { + var zid = ZitiIdentity.FromClient(createdId); + AddIdentity(zid); + LoadIdentities(true); + await serviceClient.IdentityOnOffAsync(createdId.Identifier, true); + }/* else { + ShowError("Identity Error", "Identity Id was null, please try again"); + }*/ + } catch (ServiceException se) { + ShowError(se.Message, se.AdditionalInfo); + } catch (Exception ex) { + ShowError("Unexpected Error", "Code 2:" + ex.Message); + } + HideLoad(); + } + } + + private void OnTimedEvent(object sender, EventArgs e) { + TimeSpan span = (DateTime.Now - _startDate); + int hours = span.Hours; + int minutes = span.Minutes; + int seconds = span.Seconds; + var hoursString = (hours > 9) ? hours.ToString() : "0" + hours; + var minutesString = (minutes > 9) ? minutes.ToString() : "0" + minutes; + var secondsString = (seconds > 9) ? seconds.ToString() : "0" + seconds; + ConnectedTime.Content = hoursString + ":" + minutesString + ":" + secondsString; + } + + private void InitializeTimer(int millisAgoStarted) { + _startDate = DateTime.Now.Subtract(new TimeSpan(0, 0, 0, 0, millisAgoStarted)); + _tunnelUptimeTimer = new System.Windows.Forms.Timer(); + _tunnelUptimeTimer.Interval = 100; + _tunnelUptimeTimer.Tick += OnTimedEvent; + _tunnelUptimeTimer.Enabled = true; + _tunnelUptimeTimer.Start(); + } + + async private Task DoConnectAsync() { + try { + SetNotifyIcon("green"); + TunnelConnected(true); + + for (int i = 0; i < identities.Count; i++) { + await serviceClient.IdentityOnOffAsync(identities[i].Identifier, true); + } + for (int i = 0; i < IdList.Children.Count; i++) { + IdentityItem item = IdList.Children[i] as IdentityItem; + item._identity.IsEnabled = true; + item.RefreshUI(); + } + } catch (ServiceException se) { + ShowError("Error Occurred", se.Message + " " + se.AdditionalInfo); + } catch (Exception ex) { + ShowError("Unexpected Error", "Code 3:" + ex.Message); + } + } + + async private void Disconnect(object sender, RoutedEventArgs e) { + try { + ShowLoad("Disabling Service", "Please wait for the service to stop."); + var r = await monitorClient.StopServiceAsync(); + if (r.Code != 0) { + logger.Warn("ERROR: Error:{0}, Message:{1}", r.Error, r.Message); + } else { + logger.Info("Service stopped!"); + SetNotifyIcon("white"); + } + } catch (MonitorServiceException me) { + logger.Warn("the monitor service appears offline. {0}", me); + ShowError("Error Disabling Service", "The monitor service is offline"); + } catch (Exception ex) { + logger.Error(ex, "unexpected error: {0}", ex.Message); + ShowError("Error Disabling Service", "An error occurred while trying to disable the data service. Is the monitor service running?"); + } + HideLoad(); + } + + internal void ShowLoad(string title, string msg) { + this.Dispatcher.Invoke(() => { + LoadingDetails.Text = msg; + LoadingTitle.Content = title; + LoadProgress.IsIndeterminate = true; + LoadingScreen.Visibility = Visibility.Visible; + UpdateLayout(); + }); + } + + internal void HideLoad() { + this.Dispatcher.Invoke(() => { + LoadingScreen.Visibility = Visibility.Collapsed; + LoadProgress.IsIndeterminate = false; + }); + } + + private void FormFadeOut_Completed(object sender, EventArgs e) { + closeCompleted = true; + } + private bool closeCompleted = false; + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { + if (!closeCompleted) { + FormFadeOut.Begin(); + e.Cancel = true; + } + } + + public void ShowError(string title, string message) { + this.Dispatcher.Invoke(() => { + ErrorTitle.Content = title; + ErrorDetails.Text = message; + ErrorView.Visibility = Visibility.Visible; + }); + } + + private void CloseError(object sender, RoutedEventArgs e) { + this.Dispatcher.Invoke(() => { + ErrorView.Visibility = Visibility.Collapsed; + NoServiceView.Visibility = Visibility.Collapsed; + CloseErrorButton.IsEnabled = true; + }); + } + + private void CloseApp(object sender, RoutedEventArgs e) { + Application.Current.Shutdown(); + } + + private void MainUI_Deactivated(object sender, EventArgs e) { + if (this._isAttached) { +#if DEBUG + logger.Debug("debug is enabled - windows pinned"); +#else + this.Visibility = Visibility.Collapsed; +#endif + } + } + + private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) { + Placement(); + } + + int cur = 0; + LogLevelEnum[] levels = new LogLevelEnum[] { LogLevelEnum.FATAL, LogLevelEnum.ERROR, LogLevelEnum.WARN, LogLevelEnum.INFO, LogLevelEnum.DEBUG, LogLevelEnum.TRACE, LogLevelEnum.VERBOSE }; + public LogLevelEnum NextLevel() { + cur++; + if (cur > 6) { + cur = 0; + } + return levels[cur]; + } + + private void IdList_LayoutUpdated(object sender, EventArgs e) { + Placement(); + } + + async private void CollectLogFileClick(object sender, RoutedEventArgs e) { + await CollectLogFiles(); + } + async private Task CollectLogFiles() { + MonitorServiceStatusEvent resp = await monitorClient.CaptureLogsAsync(); + if (resp != null) { + logger.Info("response: {0}", resp.Message); + } else { + ShowError("Error Collecting Feedback", "An error occurred while trying to gather feedback. Is the monitor service running?"); + } + } + + /// + /// Show the blurb as a growler notification + /// + /// The message to show + /// The url or action name to execute + public async Task ShowBlurbAsync(string message, string url, string level = "error") { + RedBlurb.Visibility = Visibility.Collapsed; + InfoBlurb.Visibility = Visibility.Collapsed; + if (level == "error") { + RedBlurb.Visibility = Visibility.Visible; + } else { + InfoBlurb.Visibility = Visibility.Visible; + } + Blurb.Content = message; + _blurbUrl = url; + BlurbArea.Visibility = Visibility.Visible; + BlurbArea.Opacity = 0; + BlurbArea.Margin = new Thickness(0, 0, 0, 0); + DoubleAnimation animation = new DoubleAnimation(1, TimeSpan.FromSeconds(.3)); + ThicknessAnimation animateThick = new ThicknessAnimation(new Thickness(15, 0, 15, 15), TimeSpan.FromSeconds(.3)); + BlurbArea.BeginAnimation(Grid.OpacityProperty, animation); + BlurbArea.BeginAnimation(Grid.MarginProperty, animateThick); + await Task.Delay(5000); + HideBlurb(); + } + + /// + /// Execute the hide operation wihout an action from the growler + /// + /// The object that was clicked + /// The click event + private void DoHideBlurb(object sender, MouseButtonEventArgs e) { + HideBlurb(); + } + + /// + /// Hide the blurb area + /// + private void HideBlurb() { + DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(.3)); + ThicknessAnimation animateThick = new ThicknessAnimation(new Thickness(0, 0, 0, 0), TimeSpan.FromSeconds(.3)); + animation.Completed += HideComplete; + BlurbArea.BeginAnimation(Grid.OpacityProperty, animation); + BlurbArea.BeginAnimation(Grid.MarginProperty, animateThick); + } + + /// + /// Hide the blurb area after the animation fades out + /// + /// The animation object + /// The completion event + private void HideComplete(object sender, EventArgs e) { + BlurbArea.Visibility = Visibility.Collapsed; + } + + /// + /// Execute a predefined action or url when the pop up is clicked + /// + /// The object that was clicked + /// The click event + private void BlurbAction(object sender, MouseButtonEventArgs e) { + if (_blurbUrl.Length > 0) { + // So this simply execute a url but you could do like if (_blurbUrl=="DoSomethingNifty") CallNifyFunction(); + if (_blurbUrl == this.RECOVER) { + this.ShowMFA(IdentityMenu.Identity, 4); + } else { + Process.Start(new ProcessStartInfo(_blurbUrl) { UseShellExecute = true }); + } + HideBlurb(); + } else { + HideBlurb(); + } + } + + private void ShowAuthenticate(ZitiIdentity identity) { + MFAAuthenticate(identity); + } + + private void ShowRecovery(ZitiIdentity identity) { + ShowMFARecoveryCodes(identity); + } + + + + + + private ICommand someCommand; + public ICommand SomeCommand { + get { + return someCommand + ?? (someCommand = new ActionCommand(() => { + if (DebugForm.Visibility == Visibility.Hidden) { + DebugForm.Visibility = Visibility.Visible; + } else { + DebugForm.Visibility = Visibility.Hidden; + } + })); + } + set { + someCommand = value; + } + } + + private void DoLoading(bool isComplete) { + if (isComplete) HideLoad(); + else ShowLoad("Loading", "Please Wait."); + } + + private void Label_MouseDoubleClick_1(object sender, MouseButtonEventArgs e) { + ShowToast("here's a toast all rightddd..."); + } + } + + public class ActionCommand : ICommand { + private readonly Action _action; + + public ActionCommand(Action action) { + _action = action; + } + + public void Execute(object parameter) { + _action(); + } + + public bool CanExecute(object parameter) { + return true; + } +#pragma warning disable CS0067 //The event 'ActionCommand.CanExecuteChanged' is never used + public event EventHandler CanExecuteChanged; +#pragma warning restore CS0067 //The event 'ActionCommand.CanExecuteChanged' is never used + } +} diff --git a/DesktopEdge/Models/ZitiIdentity.cs b/DesktopEdge/Models/ZitiIdentity.cs index 29e40d72..7c66dec0 100644 --- a/DesktopEdge/Models/ZitiIdentity.cs +++ b/DesktopEdge/Models/ZitiIdentity.cs @@ -1,170 +1,174 @@ -/* - Copyright NetFoundry Inc. - - 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace ZitiDesktopEdge.Models { - public class ZitiIdentity { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - public List Services { get; set; } - public string Name { get; set; } - public string ControllerUrl { get; set; } - - public string ContollerVersion { get; set; } - public bool IsEnabled { get; set; } - public string EnrollmentStatus { get; set; } - public string Status { get; set; } - public bool IsMFAEnabled { get; set; } - - public void MFADebug(string where) { - logger.Info($"{where}\n\tIdentifiter : {Identifier}\n\tIsMFAEnabled : {IsMFAEnabled}\n\tIsMFANeeded : {IsMFANeeded}"); - //logger.Info($"{where}\n\tIdentifiter : {Identifier}\n\tIsMFAEnabled : {IsMFAEnabled}\n\tIsMFANeeded : {IsMFANeeded}\n\tShowMFA\t : {ShowMFA}"); - } - - private bool mfaNeeded = false; - public bool IsMFANeeded { - get { return mfaNeeded; } - set { - mfaNeeded = value; - if (!mfaNeeded) { - IsTimingOut = false; - IsTimedOut = false; - WasFullNotified = false; - WasNotified = false; - } - } - } - public int MinTimeout { get; set; } - public int MaxTimeout { get; set; } - public DateTime LastUpdatedTime { get; set; } - public string TimeoutMessage { get; set; } - public bool WasNotified { get; set; } - public bool WasFullNotified { get; set; } - public string Fingerprint { get; set; } - public string Identifier { get; set; } - private bool isTimedOut = false; - - public SemaphoreSlim Mutex { get; } = new SemaphoreSlim(1); - public bool IsTimedOut { - get { return isTimedOut; } - set { - isTimedOut = value; - WasFullNotified = false; - } - } - public string[] RecoveryCodes { get; set; } - public bool IsTimingOut { get; set; } - public bool IsConnected { get; set; } - - - private bool svcFailingPostureCheck = false; - public bool HasServiceFailingPostureCheck { - get { - return svcFailingPostureCheck; - } - set { - logger.Info("Identity: {0} posture change. is a posture check failing: {1}", Name, !value); - svcFailingPostureCheck = value; - if (!value) { - IsMFANeeded = true; - } - } - } - - public bool NeedsExtAuth { get; set; } - - /// - /// Default constructor to support named initialization - /// - public ZitiIdentity() { - this.IsConnected = true; - this.Services = new List(); - } - - public ZitiIdentity(string Name, string ControllerUrl, bool IsEnabled, List Services) { - this.Name = Name; - this.Services = Services; - this.ControllerUrl = ControllerUrl; - this.IsEnabled = IsEnabled; - this.EnrollmentStatus = "Enrolled"; - this.Status = "Available"; - this.MaxTimeout = -1; - this.MinTimeout = -1; - this.LastUpdatedTime = DateTime.Now; - this.TimeoutMessage = ""; - this.RecoveryCodes = new string[0]; - this.IsTimingOut = false; - this.IsTimedOut = false; - this.IsConnected = true; - } - - public static ZitiIdentity FromClient(DataStructures.Identity id) { - ZitiIdentity zid = new ZitiIdentity() { - ControllerUrl = (id.Config == null) ? "" : id.Config.ztAPI, - ContollerVersion = id.ControllerVersion, - EnrollmentStatus = "status", - Fingerprint = id.FingerPrint, - Identifier = id.Identifier, - IsEnabled = id.Active, - Name = (string.IsNullOrEmpty(id.Name) ? id.FingerPrint : id.Name), - Status = id.Status, - RecoveryCodes = new string[0], - IsMFAEnabled = id.MfaEnabled, - IsMFANeeded = id.MfaNeeded, - IsTimedOut = false, - IsTimingOut = false, - MinTimeout = id.MinTimeout, - MaxTimeout = id.MaxTimeout, - LastUpdatedTime = id.MfaLastUpdatedTime, - TimeoutMessage = "", - IsConnected = true, - NeedsExtAuth = id.NeedsExtAuth, - }; - -#if DEBUG - zid.MFADebug("002"); -#endif - if (id.Services != null) { - foreach (var svc in id.Services) { - if (svc != null) { - var zsvc = new ZitiService(svc); - zsvc.TimeUpdated = zid.LastUpdatedTime; - zid.Services.Add(zsvc); - } - } - zid.HasServiceFailingPostureCheck = zid.Services.Any(p => !p.HasFailingPostureCheck()); - } - logger.Info("Identity: {0} updated To {1}", zid.Name, Newtonsoft.Json.JsonConvert.SerializeObject(id)); - return zid; - } - - public void ShowMFAToast(string message) { - logger.Info("Showing Notification from identity " + Name + " " + message + "."); - new Microsoft.Toolkit.Uwp.Notifications.ToastContentBuilder() - .AddText(Name + " Service Access Warning") - .AddText(message) - .AddArgument("identifier", Identifier) - .SetBackgroundActivation() - .Show(); - } - } -} +/* + Copyright NetFoundry Inc. + + 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ZitiDesktopEdge.Models { + public class ZitiIdentity { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + public List Services { get; set; } + public string Name { get; set; } + public string ControllerUrl { get; set; } + + public string ContollerVersion { get; set; } + public bool IsEnabled { get; set; } + public string EnrollmentStatus { get; set; } + public string Status { get; set; } + public bool IsMFAEnabled { get; set; } + + public void MFADebug(string where) { + logger.Info($"{where}\n\tIdentifiter : {Identifier}\n\tIsMFAEnabled : {IsMFAEnabled}\n\tIsMFANeeded : {IsMFANeeded}\n\tNeedsExtAuth : {NeedsExtAuth}"); + } + + private bool mfaNeeded = false; + public bool IsMFANeeded { + get { return mfaNeeded; } + set { + mfaNeeded = value; + if (!mfaNeeded) { + IsTimingOut = false; + IsTimedOut = false; + WasFullNotified = false; + WasNotified = false; + } + } + } + public int MinTimeout { get; set; } + public int MaxTimeout { get; set; } + public DateTime LastUpdatedTime { get; set; } + public string TimeoutMessage { get; set; } + public bool WasNotified { get; set; } + public bool WasFullNotified { get; set; } + public string Fingerprint { get; set; } + public string Identifier { get; set; } + private bool isTimedOut = false; + + public SemaphoreSlim Mutex { get; } = new SemaphoreSlim(1); + public bool IsTimedOut { + get { return isTimedOut; } + set { + isTimedOut = value; + WasFullNotified = false; + } + } + public string[] RecoveryCodes { get; set; } + public bool IsTimingOut { get; set; } + public bool IsConnected { get; set; } + + + private bool svcFailingPostureCheck = false; + public bool HasServiceFailingPostureCheck { + get { + return svcFailingPostureCheck; + } + set { + logger.Info("Identity: {0} posture change. is a posture check failing: {1}", Name, !value); + svcFailingPostureCheck = value; + if (!value) { + IsMFANeeded = true; + } + } + } + + public bool NeedsExtAuth { get; set; } + + /// + /// Default constructor to support named initialization + /// + public ZitiIdentity() { + this.IsConnected = true; + this.Services = new List(); + } + + public ZitiIdentity(string Name, string ControllerUrl, bool IsEnabled, List Services) { + this.Name = Name; + this.Services = Services; + this.ControllerUrl = ControllerUrl; + this.IsEnabled = IsEnabled; + this.EnrollmentStatus = "Enrolled"; + this.Status = "Available"; + this.MaxTimeout = -1; + this.MinTimeout = -1; + this.LastUpdatedTime = DateTime.Now; + this.TimeoutMessage = ""; + this.RecoveryCodes = new string[0]; + this.IsTimingOut = false; + this.IsTimedOut = false; + this.IsConnected = true; + } + + public static ZitiIdentity FromClient(DataStructures.Identity id) { + ZitiIdentity zid = new ZitiIdentity() { + ControllerUrl = (id.Config == null) ? "" : id.Config.ztAPI, + ContollerVersion = id.ControllerVersion, + EnrollmentStatus = "status", + Fingerprint = id.FingerPrint, + Identifier = id.Identifier, + IsEnabled = id.Active, + Name = (string.IsNullOrEmpty(id.Name) ? id.FingerPrint : id.Name), + Status = id.Status, + RecoveryCodes = new string[0], + IsMFAEnabled = id.MfaEnabled, + IsMFANeeded = id.MfaNeeded, + IsTimedOut = false, + IsTimingOut = false, + MinTimeout = id.MinTimeout, + MaxTimeout = id.MaxTimeout, + LastUpdatedTime = id.MfaLastUpdatedTime, + TimeoutMessage = "", + IsConnected = true, + NeedsExtAuth = id.NeedsExtAuth, + }; + + if (zid.Name.Contains(@"\")) { + int pos = zid.Name.LastIndexOf(@"\"); + zid.Name = zid.Name.Substring(pos + 1); + } + +#if DEBUG + zid.MFADebug("002"); +#endif + if (id.Services != null) { + foreach (var svc in id.Services) { + if (svc != null) { + var zsvc = new ZitiService(svc); + zsvc.TimeUpdated = zid.LastUpdatedTime; + zid.Services.Add(zsvc); + } + } + zid.HasServiceFailingPostureCheck = zid.Services.Any(p => !p.HasFailingPostureCheck()); + } + logger.Info("Identity: {0} updated To {1}", zid.Name, Newtonsoft.Json.JsonConvert.SerializeObject(id)); + return zid; + } + + public void ShowMFAToast(string message) { + logger.Info("Showing Notification from identity " + Name + " " + message + "."); + new Microsoft.Toolkit.Uwp.Notifications.ToastContentBuilder() + .AddText(Name + " Service Access Warning") + .AddText(message) + .AddArgument("identifier", Identifier) + .SetBackgroundActivation() + .Show(); + } + } +} diff --git a/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs b/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs index 1a32f593..7cd8d70d 100644 --- a/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs +++ b/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs @@ -1,367 +1,387 @@ -/* - Copyright NetFoundry Inc. - - 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using ZitiDesktopEdge.Models; -using ZitiDesktopEdge.ServiceClient; -using Microsoft.Toolkit.Uwp.Notifications; -using NLog; -using SWM = System.Windows.Media; -using Windows.UI.WebUI; -using Windows.Media.Protection.PlayReady; -using ZitiDesktopEdge.DataStructures; -using System.Diagnostics; - -namespace ZitiDesktopEdge { - /// - /// User Control to list Identities and give status - /// - public partial class IdentityItem : UserControl { - - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - public delegate void StatusChanged(bool attached); - public event StatusChanged OnStatusChanged; - public delegate void OnAuthenticate(ZitiIdentity identity); - public event OnAuthenticate Authenticate; - public delegate void OnIdentityChanged(ZitiIdentity identity); - public event OnIdentityChanged IdentityChanged; - private System.Windows.Forms.Timer _timer; - private System.Windows.Forms.Timer _timingTimer; - private float countdown = -1; - private float countdownComplete = -1; - private int available = 0; - - private static SWM.Color mfaOrange = SWM.Color.FromRgb(0xA1, 0x8B, 0x10); - private static SWM.Color defaultBlue = SWM.Color.FromRgb(0x00, 0x68, 0xF9); - private static SWM.Brush MFANeededBrush = new SWM.SolidColorBrush(mfaOrange); - private static SWM.Brush DefaultBrush = new SWM.SolidColorBrush(defaultBlue); - - public ZitiIdentity _identity; - public ZitiIdentity Identity { - get { - return _identity; - } - set { - _identity = value; - this.RefreshUI(); - } - } - - /// - /// Object constructor, setup the events for the control - /// - public IdentityItem() { - InitializeComponent(); - ToggleSwitch.OnToggled += ToggleIdentity; - } - - public void StopTimers() { - _timer?.Stop(); - _timingTimer?.Stop(); - } - - public int GetMaxTimeout() { - int maxto = -1; - for (int i = 0; i < _identity.Services.Count; i++) { - ZitiService info = _identity.Services[i]; - - if (info.TimeoutCalculated > -1) { - if (info.TimeoutCalculated == 0) { - available--; - } - if (info.TimeoutCalculated > maxto) maxto = info.TimeoutCalculated; - - } - logger.Trace("Max: " + _identity.Name + " " + maxto + " " + info.Name + " " + info.Timeout + " " + info.TimeoutCalculated + " " + info.TimeoutRemaining + " " + info.TimeUpdated + " " + DateTime.Now); - } - return maxto; - } - public int GetMinTimeout() { - int minto = int.MaxValue; - for (int i = 0; i < _identity.Services.Count; i++) { - ZitiService info = _identity.Services[i]; - if (info.TimeoutCalculated > -1) { - if (info.TimeoutCalculated < minto) minto = info.TimeoutCalculated; - } - // logger.Trace("Min: " + _identity.Name + " " + minto + " " + info.Name + " " + info.Timeout + " " + info.TimeoutCalculated + " " + info.TimeoutRemaining + " " + info.TimeUpdated+" "+ DateTime.Now); - } - if (minto == int.MaxValue) minto = 0; - return minto; - } - - private void MFAEnabledAndNeeded() { - MfaRequired.Visibility = Visibility.Visible; - ServiceCountArea.Visibility = Visibility.Collapsed; - MainArea.Opacity = 0.6; - ServiceCountAreaLabel.Content = "authorize"; - - float maxto = GetMaxTimeout(); - if (maxto > -1) { - if (maxto > 0) { - if (_timer != null) _timer.Stop(); - countdownComplete = maxto; - _timer = new System.Windows.Forms.Timer(); - _timer.Interval = 1000; - _timer.Tick += TimerTicked; - _timer.Start(); - logger.Info("Timer Started for full timout in " + maxto + " seconds from identity " + _identity.Name + "."); - } else { - //if (maxto == 0) ShowTimedOut(); - } - } - float minto = GetMinTimeout(); - logger.Info("Min/Max For " + _identity.Name + " " + minto + " " + maxto); - if (minto > -1) { - if (minto > 0) { - if (_timingTimer != null) _timingTimer.Stop(); - countdown = minto; - _timingTimer = new System.Windows.Forms.Timer(); - _timingTimer.Interval = 1000; - _timingTimer.Tick += TimingTimerTick; - _timingTimer.Start(); - logger.Info("Timer Started for first timout in " + minto + " seconds from identity " + _identity.Name + " value with " + _identity.MinTimeout + "."); - } else { - if (maxto > 0) { - ShowTimeout(); - } - } - } - logger.Info("RefreshUI " + _identity.Name + " Min: " + minto + " Max: " + maxto); - } - - private void MFAEnabledAndNotNeeded() { - if (_identity.IsTimedOut) { - PostureTimedOut.Visibility = Visibility.Visible; - ServiceCountAreaLabel.Content = "authorize2"; - MainArea.Opacity = 1.0; - } else { - //MfaRequired.Visibility = Visibility.Visible; - //ServiceCountAreaLabel.Content = "authenticate1"; - //MainArea.Opacity = 0.6; - MfaRequired.Visibility = Visibility.Collapsed; - } - ServiceCountBorder.Background = DefaultBrush; - } - - private void MFANotEnabledAndNotNeeded() { - ServiceCountAreaLabel.Content = "services"; - MainArea.Opacity = 1.0; - } - - private void MFANotEnabledAndNeeded() { - ServiceCount.Content = "MFA"; - ServiceCountBorder.Background = MFANeededBrush; - ServiceCountAreaLabel.Content = "disabled"; - } - - public void RefreshUI() { - if (_identity.IsConnected) { - this.IsEnabled = true; - this.Opacity = 1.0; - } else { - this.IsEnabled = false; - this.Opacity = 0.3; - } - TimerCountdown.Visibility = Visibility.Collapsed; - PostureTimedOut.Visibility = Visibility.Collapsed; - MfaRequired.Visibility = Visibility.Collapsed; - available = _identity.Services.Count; - ToggleSwitch.Enabled = _identity.IsEnabled; - ServiceCountAreaLabel.Content = "services"; - ServiceCount.Content = _identity.Services.Count.ToString(); - MainArea.Opacity = 1.0; - ServiceCountArea.Visibility = Visibility.Visible; - ServiceCountAreaLabel.Content = "services"; - // logger.Info("RefreshUI " + _identity.Name + " MFA: "+ _identity.IsMFAEnabled+" Authenticated: "+_identity.IsAuthenticated); - - ServiceCount.Content = _identity.Services.Count.ToString(); - if (_identity.IsMFAEnabled) { - if (_identity.IsMFANeeded) { - // enabled and needed = needs to be authorized. show the lock icon and tell the user to auth - MFAEnabledAndNeeded(); - } else { - // enabled and not needed = authorized. show the services should be enabled and authorized - MFAEnabledAndNotNeeded(); - } - } else { - if (_identity.IsMFANeeded) { - // not enabled and needed = show the user the MFA disabled so they can enable it - MFANotEnabledAndNeeded(); - } else { - // normal case. means no lock icon needs to be shown - MFANotEnabledAndNotNeeded(); - } - } - - if (_identity.NeedsExtAuth) { - //show ext auth - ExtAuthRequired.Visibility = Visibility.Visible; - ServiceCountArea.Visibility = Visibility.Collapsed; - } else { - //hide ext auth - ExtAuthRequired.Visibility = Visibility.Collapsed; - ServiceCountArea.Visibility = Visibility.Visible; - } - - IdName.Content = _identity.Name; - IdUrl.Content = _identity.ControllerUrl; - if (_identity.ContollerVersion != null && _identity.ContollerVersion.Length > 0) IdUrl.Content = _identity.ControllerUrl + " at " + _identity.ContollerVersion; - - ToggleStatus.Content = ((ToggleSwitch.Enabled) ? "ENABLED" : "DISABLED"); - } - - private void TimingTimerTick(object sender, EventArgs e) { - available = _identity.Services.Count; - GetMaxTimeout(); - TimerCountdown.Visibility = Visibility.Collapsed; - if (countdown > -1) { - countdown--; - logger.Trace("CountDown " + countdown + " seconds from identity " + _identity.Name + "."); - if (countdown > 0) { - TimeSpan t = TimeSpan.FromSeconds(countdown); - string answer = t.Seconds + " seconds"; - if (t.Days > 0) answer = t.Days + " days " + t.Hours + " hours " + t.Minutes + " minutes " + t.Seconds + " seconds"; - else { - if (t.Hours > 0) answer = t.Hours + " hours " + t.Minutes + " minutes " + t.Seconds + " seconds"; - else { - if (t.Minutes > 0) answer = t.Minutes + " minutes " + t.Seconds + " seconds"; - } - } - if (countdown <= 1200) { - ShowTimeout(); - - if (!_identity.WasNotified) { - _identity.WasNotified = true; - _identity.ShowMFAToast("The services for " + _identity.Name + " will start to time out in " + answer); - } - } - - if (available < _identity.Services.Count) MainArea.ToolTip = (_identity.Services.Count - available) + " of " + _identity.Services.Count + " services have timed out."; - else MainArea.ToolTip = "Some or all of the services will be timing out in " + answer; - } else { - ShowTimeout(); - MainArea.ToolTip = (_identity.Services.Count - available) + " of " + _identity.Services.Count + " services have timed out."; - ServiceCountAreaLabel.Content = available + "/" + _identity.Services.Count; - } - } else { - ShowTimeout(); - MainArea.ToolTip = "Some or all of the services have timed out."; - ServiceCountAreaLabel.Content = available + "/" + _identity.Services.Count; - } - } - - private void ShowTimeout() { - TimerCountdown.Visibility = Visibility.Visible; - ServiceCountArea.Visibility = Visibility.Collapsed; - MfaRequired.Visibility = Visibility.Collapsed; - ServiceCountAreaLabel.Content = available + "/" + _identity.Services.Count; - if (!_identity.WasNotified) { - if (available < _identity.Services.Count) { - _identity.WasNotified = true; - _identity.ShowMFAToast((_identity.Services.Count - available) + " of " + _identity.Services.Count + " services have timed out."); - } - _identity.IsTimingOut = true; - - this.IdentityChanged?.Invoke(_identity); - } - } - - private void ShowTimedOut() { - _identity.Mutex.Wait(); - Console.WriteLine(_identity.Mutex.GetHashCode()); - if (!_identity.WasFullNotified) { - _identity.WasFullNotified = true; - _identity.ShowMFAToast("All of the services with a timeout set for the identity " + _identity.Name + " have timed out"); - RefreshUI(); - if (_timer != null) _timer.Stop(); - } - _identity.Mutex.Release(); - } - - private void TimerTicked(object sender, EventArgs e) { - if (countdownComplete > -1) { - countdownComplete--; - if (countdownComplete <= 0) ShowTimedOut(); - } - } - - async private void ToggleIdentity(bool on) { - try { - if (OnStatusChanged != null) { - OnStatusChanged(on); - } - DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; - DataStructures.Identity id = await client.IdentityOnOffAsync(_identity.Identifier, on); - this.Identity.IsEnabled = on; - if (on) { - ToggleStatus.Content = "ENABLED"; - } else { - ToggleStatus.Content = "DISABLED"; - } - } catch (DataStructures.ServiceException se) { - MessageBox.Show(se.AdditionalInfo, se.Message); - } catch (Exception ex) { - MessageBox.Show("Error", ex.Message); - } - } - - private void Canvas_MouseEnter(object sender, MouseEventArgs e) { - OverState.Opacity = 0.2; - } - - private void Canvas_MouseLeave(object sender, MouseEventArgs e) { - OverState.Opacity = 0; - } - - private void OpenDetails(object sender, MouseButtonEventArgs e) { - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; - deets.SelectedIdentity = this; - deets.Identity = this.Identity; - } - - private void MFAAuthenticate(object sender, MouseButtonEventArgs e) { - this.Authenticate?.Invoke(_identity); - } - - private void ToggledSwitch(object sender, MouseButtonEventArgs e) { - ToggleSwitch.Toggle(); - } - - private void DoMFAOrOpen(object sender, MouseButtonEventArgs e) { - if (MfaRequired.Visibility == Visibility.Visible || TimerCountdown.Visibility == Visibility.Visible || PostureTimedOut.Visibility == Visibility.Visible) { - MFAAuthenticate(sender, e); - } else if (ExtAuthRequired.Visibility == Visibility.Visible) { - CompleteExtAuth(sender, e); - } else { - OpenDetails(sender, e); - } - } - - async private void CompleteExtAuth(object sender, MouseButtonEventArgs e) { - try { - DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; - ExternalAuthLoginResponse resp = await client.ExternalAuthLogin(_identity.Identifier); - Console.WriteLine(resp.Data.url); - Process.Start(resp.Data.url); - } catch (Exception ex) { - logger.Error("unexpected error!", ex); - } - } - } -} +/* + Copyright NetFoundry Inc. + + 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.ServiceClient; +using NLog; +using SWM = System.Windows.Media; +using ZitiDesktopEdge.DataStructures; +using System.Diagnostics; +using System.Web.UI; + +namespace ZitiDesktopEdge { + /// + /// User Control to list Identities and give status + /// + public partial class IdentityItem : System.Windows.Controls.UserControl { + + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + public delegate void StatusChanged(bool attached); + public event StatusChanged OnStatusChanged; + public delegate void OnAuthenticate(ZitiIdentity identity); + public event OnAuthenticate Authenticate; + public delegate void OnIdentityChanged(ZitiIdentity identity); + public event OnIdentityChanged IdentityChanged; + private System.Windows.Forms.Timer _timer; + private System.Windows.Forms.Timer _timingTimer; + private float countdown = -1; + private float countdownComplete = -1; + private int available = 0; + + private static SWM.Color mfaOrange = SWM.Color.FromRgb(0xA1, 0x8B, 0x10); + private static SWM.Color defaultBlue = SWM.Color.FromRgb(0x00, 0x68, 0xF9); + private static SWM.Brush MFANeededBrush = new SWM.SolidColorBrush(mfaOrange); + private static SWM.Brush DefaultBrush = new SWM.SolidColorBrush(defaultBlue); + + public ZitiIdentity _identity; + public ZitiIdentity Identity { + get { + return _identity; + } + set { + _identity = value; + this.RefreshUI(); + } + } + + /// + /// Object constructor, setup the events for the control + /// + public IdentityItem() { + InitializeComponent(); + ToggleSwitch.OnToggled += ToggleIdentity; + } + + public void StopTimers() { + _timer?.Stop(); + _timingTimer?.Stop(); + } + + public int GetMaxTimeout() { + int maxto = -1; + for (int i = 0; i < _identity.Services.Count; i++) { + ZitiService info = _identity.Services[i]; + + if (info.TimeoutCalculated > -1) { + if (info.TimeoutCalculated == 0) { + available--; + } + if (info.TimeoutCalculated > maxto) maxto = info.TimeoutCalculated; + + } + logger.Trace("Max: " + _identity.Name + " " + maxto + " " + info.Name + " " + info.Timeout + " " + info.TimeoutCalculated + " " + info.TimeoutRemaining + " " + info.TimeUpdated + " " + DateTime.Now); + } + return maxto; + } + public int GetMinTimeout() { + int minto = int.MaxValue; + for (int i = 0; i < _identity.Services.Count; i++) { + ZitiService info = _identity.Services[i]; + if (info.TimeoutCalculated > -1) { + if (info.TimeoutCalculated < minto) minto = info.TimeoutCalculated; + } + // logger.Trace("Min: " + _identity.Name + " " + minto + " " + info.Name + " " + info.Timeout + " " + info.TimeoutCalculated + " " + info.TimeoutRemaining + " " + info.TimeUpdated+" "+ DateTime.Now); + } + if (minto == int.MaxValue) minto = 0; + return minto; + } + + private void MFAEnabledAndNeeded() { + MfaRequired.Visibility = Visibility.Visible; + ServiceCountArea.Visibility = Visibility.Collapsed; + MainArea.Opacity = 0.6; + ServiceCountAreaLabel.Content = "authorize"; + + float maxto = GetMaxTimeout(); + if (maxto > -1) { + if (maxto > 0) { + if (_timer != null) _timer.Stop(); + countdownComplete = maxto; + _timer = new System.Windows.Forms.Timer(); + _timer.Interval = 1000; + _timer.Tick += TimerTicked; + _timer.Start(); + logger.Info("Timer Started for full timout in " + maxto + " seconds from identity " + _identity.Name + "."); + } else { + //if (maxto == 0) ShowTimedOut(); + } + } + float minto = GetMinTimeout(); + logger.Info("Min/Max For " + _identity.Name + " " + minto + " " + maxto); + if (minto > -1) { + if (minto > 0) { + if (_timingTimer != null) _timingTimer.Stop(); + countdown = minto; + _timingTimer = new System.Windows.Forms.Timer(); + _timingTimer.Interval = 1000; + _timingTimer.Tick += TimingTimerTick; + _timingTimer.Start(); + logger.Info("Timer Started for first timout in " + minto + " seconds from identity " + _identity.Name + " value with " + _identity.MinTimeout + "."); + } else { + if (maxto > 0) { + ShowTimeout(); + } + } + } + logger.Info("RefreshUI " + _identity.Name + " Min: " + minto + " Max: " + maxto); + } + + private void MFAEnabledAndNotNeeded() { + if (_identity.IsTimedOut) { + PostureTimedOut.Visibility = Visibility.Visible; + ServiceCountAreaLabel.Content = "authorize2"; + MainArea.Opacity = 1.0; + } else { + //MfaRequired.Visibility = Visibility.Visible; + //ServiceCountAreaLabel.Content = "authenticate1"; + //MainArea.Opacity = 0.6; + MfaRequired.Visibility = Visibility.Collapsed; + } + ServiceCountBorder.Background = DefaultBrush; + } + + private void MFANotEnabledAndNotNeeded() { + ServiceCountAreaLabel.Content = "services"; + MainArea.Opacity = 1.0; + } + + private void MFANotEnabledAndNeeded() { + ServiceCount.Content = "MFA"; + ServiceCountBorder.Background = MFANeededBrush; + ServiceCountAreaLabel.Content = "disabled"; + } + + public void RefreshUI() { + if (_identity.IsConnected) { + this.IsEnabled = true; + this.Opacity = 1.0; + } else { + this.IsEnabled = false; + this.Opacity = 0.3; + } + TimerCountdown.Visibility = Visibility.Collapsed; + PostureTimedOut.Visibility = Visibility.Collapsed; + MfaRequired.Visibility = Visibility.Collapsed; + available = _identity.Services.Count; + ToggleSwitch.Enabled = _identity.IsEnabled; + ServiceCountAreaLabel.Content = "services"; + ServiceCount.Content = _identity.Services.Count.ToString(); + MainArea.Opacity = 1.0; + ServiceCountArea.Visibility = Visibility.Visible; + ServiceCountAreaLabel.Content = "services"; + // logger.Info("RefreshUI " + _identity.Name + " MFA: "+ _identity.IsMFAEnabled+" Authenticated: "+_identity.IsAuthenticated); + + ServiceCount.Content = _identity.Services.Count.ToString(); + if (_identity.IsMFAEnabled) { + if (_identity.IsMFANeeded) { + // enabled and needed = needs to be authorized. show the lock icon and tell the user to auth + MFAEnabledAndNeeded(); + } else { + // enabled and not needed = authorized. show the services should be enabled and authorized + MFAEnabledAndNotNeeded(); + } + } else { + if (_identity.IsMFANeeded) { + // not enabled and needed = show the user the MFA disabled so they can enable it + MFANotEnabledAndNeeded(); + } else { + // normal case. means no lock icon needs to be shown + MFANotEnabledAndNotNeeded(); + } + } + + int idViewState = CalculateIdentityState(_identity); + + if (idViewState == 0) { + ExtAuthRequired.Visibility = Visibility.Collapsed; + MfaRequired.Visibility = Visibility.Collapsed; + ServiceCountArea.Visibility = Visibility.Visible; + } else if (idViewState % (int)IdentityStates.NeedsExtAuth == 0) { + ExtAuthRequired.Visibility = Visibility.Visible; + MfaRequired.Visibility = Visibility.Collapsed; + ServiceCountArea.Visibility = Visibility.Collapsed; + } else if (idViewState % (int)IdentityStates.NeedsMfa == 0) { + ExtAuthRequired.Visibility = Visibility.Collapsed; + MfaRequired.Visibility = Visibility.Visible; + ServiceCountArea.Visibility = Visibility.Collapsed; + } + + IdName.Content = _identity.Name; + IdUrl.Content = _identity.ControllerUrl; + if (_identity.ContollerVersion != null && _identity.ContollerVersion.Length > 0) IdUrl.Content = _identity.ControllerUrl + " at " + _identity.ContollerVersion; + + ToggleStatus.Content = ((ToggleSwitch.Enabled) ? "ENABLED" : "DISABLED"); + } + + private int CalculateIdentityState(ZitiIdentity id) { + int ret = 0; + if (id.NeedsExtAuth) { + ret += (int)IdentityStates.NeedsExtAuth; + } + if (id.IsMFANeeded) { + ret += (int)IdentityStates.NeedsMfa; + } + return ret; + } + + enum IdentityStates { + NeedsMfa = 1, + NeedsExtAuth = 2, + } + + private void TimingTimerTick(object sender, EventArgs e) { + available = _identity.Services.Count; + GetMaxTimeout(); + TimerCountdown.Visibility = Visibility.Collapsed; + if (countdown > -1) { + countdown--; + logger.Trace("CountDown " + countdown + " seconds from identity " + _identity.Name + "."); + if (countdown > 0) { + TimeSpan t = TimeSpan.FromSeconds(countdown); + string answer = t.Seconds + " seconds"; + if (t.Days > 0) answer = t.Days + " days " + t.Hours + " hours " + t.Minutes + " minutes " + t.Seconds + " seconds"; + else { + if (t.Hours > 0) answer = t.Hours + " hours " + t.Minutes + " minutes " + t.Seconds + " seconds"; + else { + if (t.Minutes > 0) answer = t.Minutes + " minutes " + t.Seconds + " seconds"; + } + } + if (countdown <= 1200) { + ShowTimeout(); + + if (!_identity.WasNotified) { + _identity.WasNotified = true; + _identity.ShowMFAToast("The services for " + _identity.Name + " will start to time out in " + answer); + } + } + + if (available < _identity.Services.Count) MainArea.ToolTip = (_identity.Services.Count - available) + " of " + _identity.Services.Count + " services have timed out."; + else MainArea.ToolTip = "Some or all of the services will be timing out in " + answer; + } else { + ShowTimeout(); + MainArea.ToolTip = (_identity.Services.Count - available) + " of " + _identity.Services.Count + " services have timed out."; + ServiceCountAreaLabel.Content = available + "/" + _identity.Services.Count; + } + } else { + ShowTimeout(); + MainArea.ToolTip = "Some or all of the services have timed out."; + ServiceCountAreaLabel.Content = available + "/" + _identity.Services.Count; + } + } + + private void ShowTimeout() { + TimerCountdown.Visibility = Visibility.Visible; + ServiceCountArea.Visibility = Visibility.Collapsed; + MfaRequired.Visibility = Visibility.Collapsed; + ServiceCountAreaLabel.Content = available + "/" + _identity.Services.Count; + if (!_identity.WasNotified) { + if (available < _identity.Services.Count) { + _identity.WasNotified = true; + _identity.ShowMFAToast((_identity.Services.Count - available) + " of " + _identity.Services.Count + " services have timed out."); + } + _identity.IsTimingOut = true; + + this.IdentityChanged?.Invoke(_identity); + } + } + + private void ShowTimedOut() { + _identity.Mutex.Wait(); + Console.WriteLine(_identity.Mutex.GetHashCode()); + if (!_identity.WasFullNotified) { + _identity.WasFullNotified = true; + _identity.ShowMFAToast("All of the services with a timeout set for the identity " + _identity.Name + " have timed out"); + RefreshUI(); + if (_timer != null) _timer.Stop(); + } + _identity.Mutex.Release(); + } + + private void TimerTicked(object sender, EventArgs e) { + if (countdownComplete > -1) { + countdownComplete--; + if (countdownComplete <= 0) ShowTimedOut(); + } + } + + async private void ToggleIdentity(bool on) { + try { + if (OnStatusChanged != null) { + OnStatusChanged(on); + } + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + DataStructures.Identity id = await client.IdentityOnOffAsync(_identity.Identifier, on); + this.Identity.IsEnabled = on; + if (on) { + ToggleStatus.Content = "ENABLED"; + } else { + ToggleStatus.Content = "DISABLED"; + } + } catch (DataStructures.ServiceException se) { + MessageBox.Show(se.AdditionalInfo, se.Message); + } catch (Exception ex) { + MessageBox.Show("Error", ex.Message); + } + } + + private void Canvas_MouseEnter(object sender, MouseEventArgs e) { + OverState.Opacity = 0.2; + } + + private void Canvas_MouseLeave(object sender, MouseEventArgs e) { + OverState.Opacity = 0; + } + + private void OpenDetails(object sender, MouseButtonEventArgs e) { + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + deets.SelectedIdentity = this; + deets.Identity = this.Identity; + } + + private void MFAAuthenticate(object sender, MouseButtonEventArgs e) { + this.Authenticate?.Invoke(_identity); + } + + private void ToggledSwitch(object sender, MouseButtonEventArgs e) { + ToggleSwitch.Toggle(); + } + + private void DoMFAOrOpen(object sender, MouseButtonEventArgs e) { + if (MfaRequired.Visibility == Visibility.Visible || TimerCountdown.Visibility == Visibility.Visible || PostureTimedOut.Visibility == Visibility.Visible) { + MFAAuthenticate(sender, e); + } else if (ExtAuthRequired.Visibility == Visibility.Visible) { + CompleteExtAuth(sender, e); + } else { + OpenDetails(sender, e); + } + } + + async private void CompleteExtAuth(object sender, MouseButtonEventArgs e) { + try { + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + ExternalAuthLoginResponse resp = await client.ExternalAuthLogin(_identity.Identifier); + Console.WriteLine(resp.Data?.url); + Process.Start(resp.Data.url); + } catch (Exception ex) { + logger.Error("unexpected error!", ex); + } + } + } +} diff --git a/Installer/build.ps1 b/Installer/build.ps1 index 3d423b7a..24cd8bcb 100644 --- a/Installer/build.ps1 +++ b/Installer/build.ps1 @@ -17,6 +17,7 @@ function verifyFile($path) { } } +echo "" echo "========================== build.ps1 begins ==========================" $invocation = (Get-Variable MyInvocation).Value $scriptPath = Split-Path $invocation.MyCommand.Path @@ -27,43 +28,77 @@ $ADV_INST_HOME = "C:\Program Files (x86)\Caphyon\Advanced Installer ${ADV_INST_V $SIGNTOOL="${ADV_INST_HOME}\third-party\winsdk\x64\signtool.exe" $ADVINST = "${ADV_INST_HOME}\bin\x86\AdvancedInstaller.com" $ADVPROJECT = "${scriptPath}\ZitiDesktopEdge.aip" -$ZITI_EDGE_TUNNEL_VERSION="v2.0.0-alpha24" +$ZITI_EDGE_TUNNEL_VERSION="v1.2.2" echo "Cleaning previous build folder if it exists" Remove-Item "${buildPath}" -r -ErrorAction Ignore mkdir "${buildPath}" -ErrorAction Ignore > $null $global:ProgressPreference = "SilentlyContinue" -$destination="${scriptPath}\zet.zip" - +$zetDownloadLoc="${scriptPath}\build\zet" +mkdir "${zetDownloadLoc}" -ErrorAction Ignore > $null +$destination="${zetDownloadLoc}\${ZITI_EDGE_TUNNEL_VERSION}-zet.zip" +$unzip = $true if($null -eq $env:ZITI_EDGE_TUNNEL_BUILD) { if($null -eq $env:ZITI_EDGE_TUNNEL_VERSION) { # use the default $ZITI_EDGE_TUNNEL_VERSION } else { $ZITI_EDGE_TUNNEL_VERSION=$env:ZITI_EDGE_TUNNEL_VERSION } - echo "========================== fetching ziti-edge-tunnel ==========================" - $zet_dl="https://github.com/openziti/ziti-tunnel-sdk-c/releases/download/${ZITI_EDGE_TUNNEL_VERSION}/ziti-edge-tunnel-Windows_x86_64.zip" - echo "Beginning to download ziti-edge-tunnel from ${zet_dl} to ${destination}" - echo "" - $response = Invoke-WebRequest $zet_dl -OutFile "${destination}" + if (Test-Path ${destination} -PathType Container) { + Write-Host -ForegroundColor Yellow "ziti-edge-tunnel.zip exists and won't be downloaded again: ${destination}" + } else { + echo "========================== fetching ziti-edge-tunnel ==========================" + $zet_dl="https://github.com/openziti/ziti-tunnel-sdk-c/releases/download/${ZITI_EDGE_TUNNEL_VERSION}/ziti-edge-tunnel-Windows_x86_64.zip" + echo "Beginning to download ziti-edge-tunnel from ${zet_dl} to ${destination}" + echo "" + $response = Invoke-WebRequest $zet_dl -OutFile "${destination}" + } } else { echo "========================== using locally defined ziti-edge-tunnel ==========================" $zet_dl="${env:ZITI_EDGE_TUNNEL_BUILD}" - echo "Sourcing ziti-edge-tunnel from ${zet_dl}" + echo "Using ziti-edge-tunnel declared from ${zet_dl}" echo "" if ($SourcePath -match "^https?://") { $response = Invoke-WebRequest -Uri "${zet_dl}" -OutFile "${destination}" } else { - $response = Copy-Item -Path "${zet_dl}" -Destination "${destination}" -ErrorAction Stop + echo "Determining if the location is a directory or zip file" + if (Test-Path $zet_dl -PathType Container) { + $unzip = $false + } elseif ($zet_dl -match '\.zip$') { + echo "Copying zip file to destination" + echo " FROM: ${zet_dl}" + echo " TO: ${destination}" + $response = Copy-Item -Path "${zet_dl}" -Destination "${destination}" -ErrorAction Stop + } else { + Write-Host -ForegroundColor Red "Unknown type. Expected either a .zip file or a directory:" + Write-Host -ForegroundColor Red " - ${zet_dl}" + exit 1 + } } } -verifyFile("${destination}") -echo "Expanding downloaded file..." -Expand-Archive -Path "${destination}" -Force -DestinationPath "${buildPath}\service" -echo "expanded ${destination} file to ${buildPath}\service" +if($unzip) { + verifyFile("${destination}") + echo "Expanding downloaded file..." + Expand-Archive -Path "${destination}" -Force -DestinationPath "${buildPath}\service" + echo "expanded ${destination} file to ${buildPath}\service" +} else { + if (Test-Path -Path "${buildPath}\service") { + echo "removing old service folder at: ${buildPath}\service" + Remove-Item -Path "${buildPath}\service" -Recurse -Force -ErrorAction SilentlyContinue + } + + echo "creating new service directory: ${buildPath}\service" + New-Item -Path "${buildPath}\service" -ItemType Directory | Out-Null + + echo "Copying files from directory to destination" + echo " FROM: ${zet_dl}\*" + echo " TO: ${buildPath}\service\" + $response = Copy-Item -Path "${zet_dl}\wintun.dll" -Destination "${buildPath}\service\wintun.dll" -ErrorAction Stop -Force + $response = Copy-Item -Path "${zet_dl}\ziti-edge-tunnel.exe" -Destination "${buildPath}\service\ziti-edge-tunnel.exe" -ErrorAction Stop -Force +} echo "========================== building and moving the custom signing tool ==========================" dotnet build -c Release "${checkoutRoot}/AWSSigner.NET\AWSSigner.NET.csproj" @@ -75,8 +110,12 @@ $env:SIGNTOOL_PATH="${SIGNTOOL}" Push-Location ${checkoutRoot} +if ($version -eq "") { + $version=(Get-Content -Path ${checkoutRoot}\version) +} + echo "Updating the version for UI and Installer" -.\update-versions.ps1 +.\update-versions.ps1 $version echo "Restoring the .NET project" nuget restore .\ZitiDesktopEdge.sln @@ -86,10 +125,6 @@ msbuild ZitiDesktopEdge.sln /property:Configuration=Release Pop-Location -if ($version -eq "") { - $version=(Get-Content -Path ${checkoutRoot}\version) -} - echo "Building VERSION $version" if($null -ne $env:ZITI_DESKTOP_EDGE_VERSION) { @@ -122,7 +157,7 @@ $exeAbsPath="${outputPath}\${exeName}" if($null -eq $env:AWS_KEY_ID) { echo "" - echo "AWS_KEY_ID not set. __THE BINARY WILL NOT BE SIGNED!__" + echo "AWS_KEY_ID not set. __THE BINARY WILL NOT BE SIGNED!__" echo "" } @@ -137,7 +172,6 @@ if($null -eq $env:OPENZITI_P12_PASS_2024) { } (Get-FileHash "${exeAbsPath}").Hash > "${scriptPath}\Output\Ziti Desktop Edge Client-${version}.exe.sha256" -echo "========================== build.ps1 completed ==========================" $outputPath = "${scriptPath}\Output\Ziti Desktop Edge Client-${version}.exe.json" & .\Installer\output-build-json.ps1 -version $version -url $url -stream $stream -published_at $published_at -outputPath $outputPath -versionQualifier $versionQualifier @@ -151,4 +185,7 @@ copy $outputPath "$checkoutRoot\release-streams\beta.json" if($revertGitAfter) { git checkout DesktopEdge/Properties/AssemblyInfo.cs ZitiUpdateService/Properties/AssemblyInfo.cs Installer/ZitiDesktopEdge.aip -} \ No newline at end of file +} + + +echo "========================== build.ps1 completed ==========================" diff --git a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs index 6d551706..0c3efd3e 100644 --- a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs +++ b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs @@ -239,6 +239,7 @@ public class IdentifierPayload { public class EnrollIdentifierPayload { public string JwtFileName { get; set; } public string JwtContent { get; set; } + public bool UseKeychain { get; set; } } public class EnrollIdentifierFunction : ServiceFunction { diff --git a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs index 032478f0..48e8df1f 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs @@ -159,7 +159,8 @@ async public Task AddIdentityAsync(string jwtFileName, bool activate, Command = "AddIdentity", Data = new EnrollIdentifierPayload() { JwtFileName = jwtFileName, - JwtContent = jwtContent + JwtContent = jwtContent, + // future use UseKeychain = true, } }; diff --git a/ZitiDesktopEdge.sln b/ZitiDesktopEdge.sln index f6986126..16d15407 100644 --- a/ZitiDesktopEdge.sln +++ b/ZitiDesktopEdge.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build-test-release.ps1 = build-test-release.ps1 Installer\build.ps1 = Installer\build.ps1 .github\workflows\installer.build.yml = .github\workflows\installer.build.yml + manual-testing.md = manual-testing.md .github\workflows\mattermost-ziti-webhook.yml = .github\workflows\mattermost-ziti-webhook.yml Installer\output-build-json.ps1 = Installer\output-build-json.ps1 README.md = README.md diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index fed183f0..2c7835d7 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -1,1083 +1,1083 @@ -/* - Copyright NetFoundry Inc. - - 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 - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -using System; -using System.Diagnostics; -using System.Linq; -using System.ServiceProcess; -using System.IO; -using System.Timers; -using System.Configuration; -using System.Threading.Tasks; -using System.Threading; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using System.IO.Compression; - -using ZitiDesktopEdge.DataStructures; -using ZitiDesktopEdge.ServiceClient; -using ZitiDesktopEdge.Server; -using ZitiDesktopEdge.Utility; - -using NLog; -using Newtonsoft.Json; -using System.Net; -using DnsClient; -using DnsClient.Protocol; -using ZitiUpdateService.Utils; -using ZitiUpdateService.Checkers; -using System.Security.Policy; -using Newtonsoft.Json.Linq; -using System.Runtime.Remoting.Messaging; - -#if !SKIPUPDATE -using ZitiUpdateService.Checkers.PeFile; -#endif - -namespace ZitiUpdateService { - public partial class UpdateService : ServiceBase { - private const string betaStreamMarkerFile = "use-beta-stream.txt"; - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static Settings CurrentSettings = new Settings(true); - - public bool IsBeta { - get { - return File.Exists(Path.Combine(exeLocation, betaStreamMarkerFile)); - } - private set { } - } - - - private System.Timers.Timer _updateTimer = new System.Timers.Timer(); - private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); - - private string exeLocation = null; - - private DataClient dataClient = new DataClient("monitor service"); - private bool running = false; - private string asmDir = null; - private string updateFolder = null; - private string filePrefix = "Ziti.Desktop.Edge.Client-"; - private Version assemblyVersion = null; - - private ServiceController controller; - private IPCServer svr = new IPCServer(); - private Task ipcServer = null; - private Task eventServer = null; - - private const int zetHealthcheckInterval = 5; - private SemaphoreSlim zetSemaphore = new SemaphoreSlim(1, 1); - private System.Timers.Timer zetHealthcheck = new System.Timers.Timer(); - private int zetFailedCheckCounter = 0; - - private UpdateCheck lastUpdateCheck; - private InstallationNotificationEvent lastInstallationNotification; - - public UpdateService() { - InitializeComponent(); - - CurrentSettings.Load(); - CurrentSettings.OnConfigurationChange += CurrentSettings_OnConfigurationChange; - - base.CanHandlePowerEvent = true; - - exeLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - Logger.Info("Initializing"); - dataClient.OnClientConnected += Svc_OnClientConnected; - dataClient.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; - dataClient.OnClientDisconnected += Svc_OnClientDisconnected; - dataClient.OnShutdownEvent += Svc_OnShutdownEvent; - dataClient.OnLogLevelEvent += ServiceClient_OnLogLevelEvent; - dataClient.OnNotificationEvent += ServiceClient_OnNotificationEvent; - - svr.CaptureLogs = CaptureLogs; - svr.SetLogLevel = SetLogLevel; - svr.SetReleaseStream = SetReleaseStream; - svr.DoUpdateCheck = DoUpdateCheck; - svr.TriggerUpdate = TriggerUpdate; - svr.SetAutomaticUpdateDisabled = SetAutomaticUpdateDisabled; - svr.SetAutomaticUpdateURL = SetAutomaticUpdateURL; - - string assemblyVersionStr = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? - assemblyVersion = new Version(assemblyVersionStr); - asmDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - updateFolder = Path.Combine(asmDir, "updates"); - if (!Directory.Exists(updateFolder)) { - Directory.CreateDirectory(updateFolder); - } - } - - private SvcResponse SetAutomaticUpdateURL(string url) { - SvcResponse failure = new SvcResponse(); - failure.Code = (int)ErrorCodes.URL_INVALID; - failure.Error = $"The url supplied is invalid: \n{url}\n"; - failure.Message = "Failure"; - - - SvcResponse r = new SvcResponse(); - if (url == null || !url.StartsWith("http")) { - return failure; - } else { - // check the url exists and appears correct... - var check = new GithubCheck(assemblyVersion, url); - - if (check != null) { - var v = check.GetNextVersion(); - - if (v == null) { - return failure; - } - if (v.Revision.ToString().Trim() == "") { - return failure; - } - } - - checkUpdateImmediately(); - - CurrentSettings.AutomaticUpdateURL = url; - CurrentSettings.Write(); - r.Message = "Success"; - } - return r; - } - - private void CurrentSettings_OnConfigurationChange(object sender, ControllerEvent e) { - MonitorServiceStatusEvent evt; - if (lastInstallationNotification != null) { - evt = lastInstallationNotification; - } else { - evt = new MonitorServiceStatusEvent() { - Code = 0, - Error = "", - Message = "Configuration Changed", - Type = "Status", - Status = ServiceActions.ServiceStatus(), - ReleaseStream = IsBeta ? "beta" : "stable", - AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString(), - AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, - }; - } - Logger.Debug($"notifying consumers of change to CurrentSettings. AutomaticUpdates status = {(CurrentSettings.AutomaticUpdatesDisabled ? "disabled" : "enabled")}"); - EventRegistry.SendEventToConsumers(evt); - } - - private SvcResponse SetAutomaticUpdateDisabled(bool disabled) { - if (lastInstallationNotification != null) { - lastInstallationNotification.AutomaticUpgradeDisabled = disabled.ToString(); - } - CurrentSettings.AutomaticUpdatesDisabled = disabled; - CurrentSettings.Write(); - SvcResponse r = new SvcResponse(); - r.Message = "Success"; - return r; - } - - private SvcResponse TriggerUpdate() { - SvcResponse r = new SvcResponse(); - r.Message = "Initiating Update"; - - Task.Run(() => { installZDE(lastUpdateCheck); }); - return r; - } - - - private void checkUpdateImmediately() { - try { - CheckUpdate(null, null); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error in CheckUpdate"); - } - } - - private StatusCheck DoUpdateCheck() { - StatusCheck r = new StatusCheck(); - - UpdateCheck check = getCheck(assemblyVersion); - - r.Code = check.Avail; - r.ReleaseStream = IsBeta ? "beta" : "stable"; - switch (r.Code) { - case -1: - r.Message = $"An update is available: {check.GetNextVersion()}"; - r.UpdateAvailable = true; - Logger.Debug("Update {0} is published on {1}", check.GetNextVersion(), check.PublishDate); - checkUpdateImmediately(); - break; - case 0: - r.Message = $"The current version [{assemblyVersion}] is the latest"; - break; - case 1: - r.Message = $"Your version [{assemblyVersion}] is newer than the latest release"; - break; - default: - r.Message = "Update check failed"; - break; - } - return r; - } - - private void SetLogLevel(string level) { - try { - Logger.Info("request to change log level received: {0}", level); - if (("" + level).ToLower().Trim() == "verbose") { - level = "trace"; - Logger.Info("request to change log level to verbose - but using trace instead"); - } - var l = LogLevel.FromString(level); - foreach (var rule in LogManager.Configuration.LoggingRules) { - rule.EnableLoggingForLevel(l); - rule.SetLoggingLevels(l, LogLevel.Fatal); - } - - LogManager.ReconfigExistingLoggers(); - Logger.Info("logger reconfigured to log at level: {0}", l); - } catch (Exception e) { - Logger.Error(e, "Could NOT set the log level for loggers??? {0}", e.Message); - } - } - - private void SetReleaseStream(string stream) { - string markerFile = Path.Combine(exeLocation, betaStreamMarkerFile); - if (stream == "beta") { - if (IsBeta) { - Logger.Debug("already using beta stream. No action taken"); - } else { - Logger.Info("Setting update service to use beta stream!"); - using (File.Create(markerFile)) { - - } - AccessUtils.GrantAccessToFile(markerFile); //allow anyone to delete this manually if need be... - Logger.Debug("added marker file: {0}", markerFile); - } - } else { - if (!IsBeta) { - Logger.Debug("already using release stream. No action taken"); - } else { - Logger.Info("Setting update service to use release stream!"); - if (File.Exists(markerFile)) { - File.Delete(markerFile); - Logger.Debug("removed marker file: {0}", markerFile); - } - } - } - } - - private string CaptureLogs() { - try { - string logLocation = Path.Combine(exeLocation, "logs"); - string destinationLocation = Path.Combine(exeLocation, "temp"); - string serviceLogsLocation = Path.Combine(logLocation, "service"); - string serviceLogsDest = Path.Combine(destinationLocation, "service"); - - Logger.Debug("removing leftover temp folder: {0}", destinationLocation); - try { - Directory.Delete(destinationLocation, true); - } catch { - //means it doesn't exist - } - - Directory.CreateDirectory(destinationLocation); - - Logger.Debug("copying all directories from: {0}", logLocation); - foreach (string dirPath in Directory.GetDirectories(logLocation, "*", SearchOption.AllDirectories)) { - Directory.CreateDirectory(dirPath.Replace(logLocation, destinationLocation)); - } - - Logger.Debug("copying all non-zip files from: {0}", logLocation); - foreach (string newPath in Directory.GetFiles(logLocation, "*.*", SearchOption.AllDirectories)) { - if (!newPath.EndsWith(".zip")) { - File.Copy(newPath, newPath.Replace(logLocation, destinationLocation), true); - } - } - - Logger.Debug("copying service files from: {0} to {1}", serviceLogsLocation, serviceLogsDest); - Directory.CreateDirectory(serviceLogsDest); - foreach (string newPath in Directory.GetFiles(serviceLogsLocation, "*.*", SearchOption.TopDirectoryOnly)) { - if (newPath.EndsWith(".log") || newPath.Contains("config.json")) { - Logger.Debug("copying service log: {0}", newPath); - File.Copy(newPath, newPath.Replace(serviceLogsLocation, serviceLogsDest), true); - } - } - - outputIpconfigInfo(destinationLocation); - outputSystemInfo(destinationLocation); - outputDnsCache(destinationLocation); - outputExternalIP(destinationLocation); - outputTasklist(destinationLocation); - outputRouteInfo(destinationLocation); - outputNetstatInfo(destinationLocation); - outputNrpt(destinationLocation); - - Task.Delay(500).Wait(); - - string zipName = Path.Combine(logLocation, DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + ".zip"); - ZipFile.CreateFromDirectory(destinationLocation, zipName); - - Logger.Debug("cleaning up temp folder: {0}", destinationLocation); - try { - Directory.Delete(destinationLocation, true); - } catch { - //means it doesn't exist - } - return zipName; - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error in generating system files {0}", ex.Message); - return null; - } - } - - private void outputIpconfigInfo(string destinationFolder) { - Logger.Info("capturing ipconfig information"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var ipconfigOut = Path.Combine(destinationFolder, "ipconfig.all.txt"); - Logger.Debug("copying ipconfig /all to {0}", ipconfigOut); - startInfo.Arguments = $"/C ipconfig /all > \"{ipconfigOut}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error in outputIpconfigInfo {0}", ex.Message); - } - } - - private void outputSystemInfo(string destinationFolder) { - Logger.Info("capturing systeminfo"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var sysinfoOut = Path.Combine(destinationFolder, "systeminfo.txt"); - Logger.Debug("running systeminfo to {0}", sysinfoOut); - startInfo.Arguments = $"/C systeminfo > \"{sysinfoOut}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error in outputSystemInfo {0}", ex.Message); - } - } - - private void outputDnsCache(string destinationFolder) { - Logger.Info("capturing dns cache information"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var dnsCache = Path.Combine(destinationFolder, "dnsCache.txt"); - Logger.Debug("running ipconfig /displaydns to {0}", dnsCache); - startInfo.Arguments = $"/C ipconfig /displaydns > \"{dnsCache}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error in outputDnsCache {0}", ex.Message); - } - } - - private void outputExternalIP(string destinationFolder) { - Logger.Info("capturing external IP address using nslookup command"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var extIpFile = Path.Combine(destinationFolder, "externalIP.txt"); - Logger.Debug("running nslookup myip.opendns.com. resolver1.opendns.com to {0}", extIpFile); - startInfo.Arguments = $"/C nslookup myip.opendns.com. resolver1.opendns.com > \"{extIpFile}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error in outputExternalIP {0}", ex.Message); - } - } - - private void outputTasklist(string destinationFolder) { - Logger.Info("capturing executing tasks"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var tasklistOutput = Path.Combine(destinationFolder, "tasklist.txt"); - Logger.Debug("running tasklist to {0}", tasklistOutput); - startInfo.Arguments = $"/C tasklist > \"{tasklistOutput}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error {0}", ex.Message); - } - } - - private void outputRouteInfo(string destinationFolder) { - Logger.Info("capturing network routes"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var networkRoutes = Path.Combine(destinationFolder, "network-routes.txt"); - Logger.Debug("running route print to {0}", networkRoutes); - startInfo.Arguments = $"/C route print > \"{networkRoutes}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error {0}", ex.Message); - } - } - - private void outputNetstatInfo(string destinationFolder) { - Logger.Info("capturing netstat"); - try { - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - var netstatOutput = Path.Combine(destinationFolder, "netstat.txt"); - Logger.Debug("running netstat -ano to {0}", netstatOutput); - startInfo.Arguments = $"/C netstat -ano > \"{netstatOutput}\""; - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error {0}", ex.Message); - } - } - - private void outputNrpt(string destinationFolder) { - Logger.Info("outputting NRPT rules"); - try { - Logger.Info("outputting NRPT DnsClientNrptRule"); - string nrptRuleOutput = Path.Combine(destinationFolder, "NrptRule.txt"); - Process nrptRuleProcess = new Process(); - ProcessStartInfo nrptRuleStartInfo = new ProcessStartInfo(); - nrptRuleStartInfo.WindowStyle = ProcessWindowStyle.Hidden; - nrptRuleStartInfo.FileName = "cmd.exe"; - nrptRuleStartInfo.Arguments = $"/C powershell \"Get-DnsClientNrptRule | sort -Property Namespace\" > \"{nrptRuleOutput}\""; - Logger.Info("Running: {0}", nrptRuleStartInfo.Arguments); - nrptRuleProcess.StartInfo = nrptRuleStartInfo; - nrptRuleProcess.Start(); - nrptRuleProcess.WaitForExit(); - - Logger.Info("outputting NRPT DnsClientNrptPolicy"); - string nrptOutput = Path.Combine(destinationFolder, "NrptPolicy.txt"); - Process process = new Process(); - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.FileName = "cmd.exe"; - startInfo.Arguments = $"/C powershell \"Get-DnsClientNrptPolicy | sort -Property Namespace\" > \"{nrptOutput}\""; - Logger.Info("Running: {0}", startInfo.Arguments); - process.StartInfo = startInfo; - process.Start(); - process.WaitForExit(); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error {0}", ex.Message); - } - } - - public void Debug() { - OnStart(null);// new string[] { "FilesystemCheck" }); - } - - protected override void OnStart(string[] args) { - Logger.Debug("args: {0}", args); - Logger.Info("ziti-monitor service is starting"); - - var logs = Path.Combine(exeLocation, "logs"); - addLogsFolder(exeLocation); - addLogsFolder(logs); - addLogsFolder(Path.Combine(logs, "UI")); - addLogsFolder(Path.Combine(logs, "ZitiMonitorService")); - addLogsFolder(Path.Combine(logs, "service")); - - AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiUpdateService.exe.config")); //allow anyone to change the config file - AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiUpdateService-log.config")); //allow anyone to change the log file config - AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiDesktopEdge.exe.config")); //allow anyone to change the config file - AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiDesktopEdge-log.config")); //allow anyone to change the log file config - - zetHealthcheck.Interval = zetHealthcheckInterval * 1000; - zetHealthcheck.Elapsed += zitiEdgeTunnelAlivenessCheck; - - Logger.Info("starting ipc server"); - ipcServer = svr.startIpcServerAsync(onIpcClientAsync); - Logger.Info("starting events server"); - eventServer = svr.startEventsServerAsync(onEventsClientAsync); - - Logger.Info("starting service watchers"); - if (!running) { - running = true; - Task.Run(() => { - SetupServiceWatchers(); - }); - } - Logger.Info("ziti-monitor service is initialized and running"); - base.OnStart(args); - } - - private void zitiEdgeTunnelAlivenessCheck(object sender, ElapsedEventArgs e) { - try { - if (zetSemaphore.Wait(TimeSpan.FromSeconds(zetHealthcheckInterval))) { - Logger.Trace("ziti-edge-tunnel aliveness check starts"); - dataClient.GetStatusAsync().Wait(); - zetSemaphore.Release(); - Interlocked.Exchange(ref zetFailedCheckCounter, 0); - Logger.Trace("ziti-edge-tunnel aliveness check ends successfully"); - } else { - Interlocked.Add(ref zetFailedCheckCounter, 1); - Logger.Warn("ziti-edge-tunnel aliveness check appears blocked and has been for {} times", zetFailedCheckCounter); - if (zetFailedCheckCounter > 2) { - disableHealthCheck(); - //after 3 failures, just terminate ziti-edge-tunnel - Interlocked.Exchange(ref zetFailedCheckCounter, 0); //reset the counter back to 0 - Logger.Warn("forcefully stopping ziti-edge-tunnel as it has been blocked for too long"); - stopProcessForcefully("ziti-edge-tunnel", "data service [ziti]"); - - Logger.Info("immediately restarting ziti-edge-tunnel"); - ServiceActions.StartService(); //attempt to start the service - } - } - } catch (Exception ex) { - Logger.Error("ziti-edge-tunnel aliveness check ends exceptionally: {}", ex.Message); - Logger.Error(ex); - } - } - - async private Task onEventsClientAsync(StreamWriter writer) { - try { - Logger.Info("a new events client was connected"); - //reset to release stream - //initial status when connecting the event stream - MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { - Code = 0, - Error = "", - Message = "Success", - Type = "Status", - Status = ServiceActions.ServiceStatus(), - ReleaseStream = IsBeta ? "beta" : "stable", - AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString(), - AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, - }; - await writer.WriteLineAsync(JsonConvert.SerializeObject(status)); - await writer.FlushAsync(); - - //if a new client attaches - send the last update check status - if (lastUpdateCheck != null) { - await writer.WriteLineAsync(JsonConvert.SerializeObject(lastInstallationNotification)); - await writer.FlushAsync(); - } - } catch (Exception ex) { - Logger.Error("UNEXPECTED ERROR: {}", ex); - } - } - -#pragma warning disable 1998 //This async method lacks 'await' - async private Task onIpcClientAsync(StreamWriter writer) { - Logger.Info("a new ipc client was connected"); - } -#pragma warning restore 1998 //This async method lacks 'await' - - private void addLogsFolder(string path) { - if (!Directory.Exists(path)) { - Logger.Info($"creating folder: {path}"); - Directory.CreateDirectory(path); - AccessUtils.GrantAccessToDirectory(path); - } - } - - public void WaitForCompletion() { - Task.WaitAll(ipcServer, eventServer); - } - - protected override void OnStop() { - Logger.Info("ziti-monitor OnStop was called"); - base.OnStop(); - } - - protected override void OnPause() { - Logger.Info("ziti-monitor OnPause was called"); - base.OnPause(); - } - - protected override void OnShutdown() { - Logger.Info("ziti-monitor OnShutdown was called"); - base.OnShutdown(); - } - - protected override void OnContinue() { - Logger.Info("ziti-monitor OnContinue was called"); - base.OnContinue(); - } - - protected override void OnCustomCommand(int command) { - Logger.Info("ziti-monitor OnCustomCommand was called {0}", command); - base.OnCustomCommand(command); - } - - protected override void OnSessionChange(SessionChangeDescription changeDescription) { - Logger.Info("ziti-monitor OnSessionChange was called {0}", changeDescription); - base.OnSessionChange(changeDescription); - } - - protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) { - Logger.Info("ziti-monitor OnPowerEvent was called {0}", powerStatus); - if(powerStatus == PowerBroadcastStatus.Suspend) { - // when going to sleep, make sure the healthcheck is disabled or accounts for going to sleep - disableHealthCheck(); - } - return base.OnPowerEvent(powerStatus); - } - - private void SetupServiceWatchers() { - var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); - var upInt = TimeSpan.Zero; - if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { - upInt = new TimeSpan(0, 10, 0); - } - - if (upInt.TotalMilliseconds < 10 * 60 * 1000) { - Logger.Warn("provided time [{0}] is too small. Using 10 minutes.", updateTimerInterval); -#if MOCKUPDATE || ALLOWFASTINTERVAL - Logger.Info("MOCKUPDATE detected. Not limiting check to 10 minutes"); -#else - upInt = TimeSpan.Parse("0:10:0"); -#endif - } - - _updateTimer = new System.Timers.Timer(); - _updateTimer.Elapsed += CheckUpdate; - _updateTimer.Interval = upInt.TotalMilliseconds; - _updateTimer.Enabled = true; - _updateTimer.Start(); - Logger.Info("Version Checker is running every {0} minutes", upInt.TotalMinutes); - - cleanOldLogs(asmDir); - scanForStaleDownloads(updateFolder); - - checkUpdateImmediately(); - - try { - dataClient.ConnectAsync().Wait(); - } catch { - dataClient.Reconnect(); - } - - dataClient.WaitForConnectionAsync().Wait(); - } - - private void cleanOldLogs(string whereToScan) { - //this function will be removed in the future. it's here to clean out the old ziti-monitor*log files that - //were there before the 1.5.0 release - try { - Logger.Info("Scanning for stale logs"); - foreach (var f in Directory.EnumerateFiles(whereToScan)) { - FileInfo logFile = new FileInfo(f); - if (logFile.Name.StartsWith("ziti-monitor.") && logFile.Name.EndsWith(".log")) { - Logger.Info("removing old log file: " + logFile.Name); - logFile.Delete(); - } - } - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error has occurred"); - } - } - -#if MOCKUPDATE - static DateTime mockDate = DateTime.Now; -#endif - private UpdateCheck getCheck(Version v) { -#if MOCKUPDATE - //run with MOCKUPDATE to enable debugging/mocking the update check - var check = new FilesystemCheck(v, -1, mockDate, "FilesysteCheck.download.mock.txt", new Version("2.1.4")); -#else - - if (string.IsNullOrEmpty(CurrentSettings.AutomaticUpdateURL)) { - CurrentSettings.AutomaticUpdateURL = GithubAPI.ProdUrl; - Logger.Info("Settings does not contain update url. Setting to: {}", CurrentSettings.AutomaticUpdateURL); - CurrentSettings.Write(); - } else { - Logger.Info("Settings contained a value for update url. Using: {}", CurrentSettings.AutomaticUpdateURL); - } - - var check = new GithubCheck(v, CurrentSettings.AutomaticUpdateURL); -#endif - return check; - } - - private InstallationNotificationEvent newInstallationNotificationEvent(string version) { - InstallationNotificationEvent info = new InstallationNotificationEvent() { - Code = 0, - Error = "", - Message = "InstallationUpdate", - Type = "Notification", - Status = ServiceActions.ServiceStatus(), - ReleaseStream = IsBeta ? "beta" : "stable", - AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString().ToLower(), - AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, - ZDEVersion = version, - }; - return info; - } - - private void CheckUpdate(object sender, ElapsedEventArgs e) { - if (e != null) { - Logger.Debug("Timer triggered CheckUpdate at {0}", e.SignalTime); - } - semaphore.Wait(); - - try { - Logger.Debug("checking for update"); - var check = getCheck(assemblyVersion); - - if (check.Avail >= 0) { - Logger.Debug("update check complete. no update available"); - semaphore.Release(); - return; - } - - Logger.Info("update is available."); - if (!Directory.Exists(updateFolder)) { - Directory.CreateDirectory(updateFolder); - } - InstallationNotificationEvent info = newInstallationNotificationEvent(check.GetNextVersion().ToString()); - info.PublishTime = check.PublishDate; - info.NotificationDuration = InstallationReminder(); - if (InstallationIsCritical(check.PublishDate)) { - info.InstallTime = DateTime.Now + TimeSpan.Parse("0:0:30"); - Logger.Warn("Installation is critical! for ZDE version: {0}. update published at: {1}. approximate install time: {2}", info.ZDEVersion, check.PublishDate, info.InstallTime); - NotifyInstallationUpdates(info, true); - if (CurrentSettings.AutomaticUpdatesDisabled) { - Logger.Debug("AutomaticUpdatesDisabled is set to true. Automatic update is disabled."); - } else { - Thread.Sleep(30); - installZDE(check); - } - } else { - info.InstallTime = InstallDateFromPublishDate(check.PublishDate); - Logger.Info("Installation reminder for ZDE version: {0}. update published at: {1}. approximate install time: {2}", info.ZDEVersion, check.PublishDate, info.InstallTime); - NotifyInstallationUpdates(info); - } - lastUpdateCheck = check; - lastInstallationNotification = info; - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error has occurred during the check for ZDE updates"); - } - semaphore.Release(); - } - - private void installZDE(UpdateCheck check) { - string fileDestination = Path.Combine(updateFolder, check?.FileName); - - if (check.AlreadyDownloaded(updateFolder, check.FileName)) { - Logger.Trace("package has already been downloaded to {0}", fileDestination); - } else { - Logger.Info("copying update package begins"); - check.CopyUpdatePackage(updateFolder, check.FileName); - Logger.Info("copying update package complete"); - } - - Logger.Info("package is in {0} - moving to install phase", fileDestination); - - if (!check.HashIsValid(updateFolder, check.FileName)) { - Logger.Warn("The file was downloaded but the hash is not valid. The file will be removed: {0}", fileDestination); - File.Delete(fileDestination); - return; - } - Logger.Debug("downloaded file hash was correct. update can continue."); -#if !SKIPUPDATE - try { - Logger.Info("verifying file [{}]", fileDestination); - new SignedFileValidator(fileDestination).Verify(); - Logger.Info("SignedFileValidator complete"); - - StopZiti(); - StopUI().Wait(); - - Logger.Info("Running update package: " + fileDestination); - // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well - Process.Start(fileDestination, "/passive"); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error during installation"); - } -#else - Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE of version: {} published at {}", check.GetNextVersion(), check.PublishDate); - Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE of version: {} published at {}", check.GetNextVersion(), check.PublishDate); - Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE of version: {} published at {}", check.GetNextVersion(), check.PublishDate); -#endif - } - - private bool isOlder(Version current) { - int compare = current.CompareTo(assemblyVersion); - Logger.Info("comparing current[{0}] to compare[{1}]: {2}", current.ToString(), assemblyVersion.ToString(), compare); - if (compare < 0) { - return true; - } else if (compare > 0) { - return false; - } else { - return false; - } - } - - private void scanForStaleDownloads(string folder) { - try { - if (!Directory.Exists(folder)) { - Logger.Debug("folder {0} does not exist. skipping", folder); - return; - } - Logger.Info("Scanning for stale downloads"); - foreach (var f in Directory.EnumerateFiles(folder)) { - try { - FileInfo fi = new FileInfo(f); - if (fi.Exists) { - if (fi.Name.StartsWith(filePrefix)) { - Logger.Debug("scanning for staleness: " + f); - string ver = Path.GetFileNameWithoutExtension(f).Substring(filePrefix.Length); - Version fileVersion = Version.Parse(ver); - if (isOlder(fileVersion)) { - Logger.Info("Removing old download: " + fi.Name); - fi.Delete(); - } else { - Logger.Debug("Retaining file. {1} is the same or newer than {1}", fi.Name, assemblyVersion); - } - } else { - Logger.Debug("skipping file named {0}", f); - } - } else { - Logger.Debug("file named {0} did not exist?", f); - } - } catch (Exception ex) { - Logger.Error(ex, "Unexpected exception processing {0}", f); - } - } - } catch (Exception ex) { - Logger.Error(ex, "Unexpected exception"); - } - } - - private void StopZiti() { - Logger.Info("Stopping the ziti service..."); - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - bool cleanStop = false; - if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { - try { - controller.Stop(); - Logger.Debug("Waiting for the ziti service to stop."); - controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); - Logger.Debug("The ziti service was stopped successfully."); - cleanStop = true; - } catch (Exception e) { - Logger.Error(e, "Timout while trying to stop service!"); - } - } else { - Logger.Debug("The ziti has ALREADY been stopped successfully."); - } - if (!cleanStop) { - Logger.Debug("Stopping ziti-edge-tunnel forcefully."); - stopProcessForcefully("ziti-edge-tunnel", "data service [ziti]"); - } - } - - private void stopProcessForcefully(string processName, string description) { - try { - Logger.Info("Closing the {description} process", description); - Process[] workers = Process.GetProcessesByName(processName); - if (workers.Length < 1) { - Logger.Info("No {description} process found to close.", description); - return; - } - // though strange, because we're about to kill the process, this is still - // considered 'expected' since the monitor service is shutting it down (forcefully). - // not clean is to indicate the process ended unexpectedly - dataClient.ExpectedShutdown = true; - - foreach (Process worker in workers) { - try { - Logger.Info("Killing: {0}", worker); - if (!worker.CloseMainWindow()) { - //don't care right now because when called on the UI it just gets 'hidden' - } - worker.Kill(); - worker.WaitForExit(5000); - Logger.Info("Stopping the {description} process killed", description); - worker.Dispose(); - } catch (Exception e) { - Logger.Error(e, "Unexpected error when closing the {description}!", description); - } - } - } catch (Exception e) { - Logger.Error(e, "Unexpected error when closing the {description}!", description); - } - } - - async private Task StopUI() { - //first try to ask the UI to exit: - - MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { - Code = 0, - Error = "", - Message = "Upgrading" - }; - EventRegistry.SendEventToConsumers(status); - - await Task.Delay(1000); //wait for the event to send and give the UI time to close... - - stopProcessForcefully("ZitiDesktopEdge", "UI"); - } - - private static void Svc_OnShutdownEvent(object sender, StatusEvent e) { - Logger.Info("the service is shutting down normally..."); - - MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { - Code = 0, - Error = "SERVICE DOWN", - Message = "SERVICE DOWN", - Status = ServiceActions.ServiceStatus() - }; - EventRegistry.SendEventToConsumers(status); - } - - private void ServiceClient_OnLogLevelEvent(object sender, LogLevelEvent e) { - SetLogLevel(e.LogLevel); - } - - private void ServiceClient_OnNotificationEvent(object sender, NotificationEvent e) { - Logger.Trace("Notification event but not acting: {0}", e.Op); - } - - private void Svc_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { - string dns = e?.Status?.IpInfo?.DNS; - string version = e?.Status?.ServiceVersion.Version; - string op = e?.Op; - Logger.Info($"Operation {op}. running dns: {dns} at version {version}"); - - SetLogLevel(e.Status.LogLevel); - } - - private void disableHealthCheck() { - if (zetHealthcheck.Enabled) { - zetHealthcheck.Enabled = true; - zetHealthcheck.Stop(); - Logger.Info("ziti-edge-tunnel health check disabled"); - } else { - Logger.Info("ziti-edge-tunnel health check already disabled"); - } - } - - private void enableHealthCheck() { - if (!zetHealthcheck.Enabled) { - zetHealthcheck.Enabled = false; - zetHealthcheck.Start(); - Logger.Info("ziti-edge-tunnel health check enabled"); - } else { - Logger.Info("ziti-edge-tunnel health check already enabled"); - } - } - - private void Svc_OnClientConnected(object sender, object e) { - Logger.Info("successfully connected to service"); - enableHealthCheck(); - } - - private void Svc_OnClientDisconnected(object sender, object e) { - disableHealthCheck(); //no need to healthcheck when we know it's disconnected - DataClient svc = (DataClient)sender; - if (svc.ExpectedShutdown) { - //then this is fine and expected - the service is shutting down - Logger.Info("client disconnected due to clean service shutdown"); - } else { - Logger.Error("SERVICE IS DOWN and did not exit cleanly."); - - MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { - Code = 10, - Error = "SERVICE DOWN", - Message = "SERVICE DOWN", - Type = "Status", - Status = ServiceActions.ServiceStatus(), - ReleaseStream = IsBeta ? "beta" : "stable", - AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString(), - AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, - }; - EventRegistry.SendEventToConsumers(status); - } - } - - private static void EnumerateDNS() { - var ps = System.Management.Automation.PowerShell.Create(); - ps.AddScript("Get-DnsClientServerAddress"); - var results = ps.Invoke(); - - using (StringWriter sw = new StringWriter()) { - foreach (var r in results) { - string name = (string)r.Properties["InterfaceAlias"].Value; - string[] dnses = (string[])r.Properties["ServerAddresses"].Value; - sw.WriteLine($"Interface: {name}. DNS: {string.Join(",", dnses)}"); - } - Logger.Info("DNS RESULTS:\n{0}", sw.ToString()); - } - } - - private TimeSpan InstallationReminder() { - var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationReminder"); - var reminderInt = TimeSpan.Zero; - if (!TimeSpan.TryParse(installationReminderIntervalStr, out reminderInt)) { - reminderInt = new TimeSpan(0, 1, 0); - } - return reminderInt; - } - - private DateTime InstallDateFromPublishDate(DateTime publishDate) { - var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationCritical"); - var instCritTimespan = TimeSpan.Zero; - if (!TimeSpan.TryParse(installationReminderIntervalStr, out instCritTimespan)) { - instCritTimespan = TimeSpan.Parse("7:0:0:0"); - } - return publishDate + instCritTimespan; - } - - private bool InstallationIsCritical(DateTime publishDate) { - var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationCritical"); - var instCritTimespan = TimeSpan.Zero; - if (!TimeSpan.TryParse(installationReminderIntervalStr, out instCritTimespan)) { - instCritTimespan = TimeSpan.Parse("7:0:0:0"); - } - return DateTime.Now > publishDate + instCritTimespan; - } - - private void NotifyInstallationUpdates(InstallationNotificationEvent evt) { - NotifyInstallationUpdates(evt, false); - } - - private void NotifyInstallationUpdates(InstallationNotificationEvent evt, bool force) { - try { - evt.Message = "InstallationUpdate"; - evt.Type = "Notification"; - EventRegistry.SendEventToConsumers(evt); - Logger.Debug("NotifyInstallationUpdates: sent for version {0} is sent to the events pipe...", evt.ZDEVersion); - return; - } catch (Exception e) { - Logger.Error("The notification for the installation updates for version {0} has failed: {1}", evt.ZDEVersion, e); - } - } - } -} \ No newline at end of file +/* + Copyright NetFoundry Inc. + + 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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.IO; +using System.Timers; +using System.Configuration; +using System.Threading.Tasks; +using System.Threading; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.IO.Compression; + +using ZitiDesktopEdge.DataStructures; +using ZitiDesktopEdge.ServiceClient; +using ZitiDesktopEdge.Server; +using ZitiDesktopEdge.Utility; + +using NLog; +using Newtonsoft.Json; +using System.Net; +using DnsClient; +using DnsClient.Protocol; +using ZitiUpdateService.Utils; +using ZitiUpdateService.Checkers; +using System.Security.Policy; +using Newtonsoft.Json.Linq; +using System.Runtime.Remoting.Messaging; + +#if !SKIPUPDATE +using ZitiUpdateService.Checkers.PeFile; +#endif + +namespace ZitiUpdateService { + public partial class UpdateService : ServiceBase { + private const string betaStreamMarkerFile = "use-beta-stream.txt"; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static Settings CurrentSettings = new Settings(true); + + public bool IsBeta { + get { + return File.Exists(Path.Combine(exeLocation, betaStreamMarkerFile)); + } + private set { } + } + + + private System.Timers.Timer _updateTimer = new System.Timers.Timer(); + private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); + + private string exeLocation = null; + + private DataClient dataClient = new DataClient("monitor service"); + private bool running = false; + private string asmDir = null; + private string updateFolder = null; + private string filePrefix = "Ziti.Desktop.Edge.Client-"; + private Version assemblyVersion = null; + + private ServiceController controller; + private IPCServer svr = new IPCServer(); + private Task ipcServer = null; + private Task eventServer = null; + + private const int zetHealthcheckInterval = 5; + private SemaphoreSlim zetSemaphore = new SemaphoreSlim(1, 1); + private System.Timers.Timer zetHealthcheck = new System.Timers.Timer(); + private int zetFailedCheckCounter = 0; + + private UpdateCheck lastUpdateCheck; + private InstallationNotificationEvent lastInstallationNotification; + + public UpdateService() { + InitializeComponent(); + + CurrentSettings.Load(); + CurrentSettings.OnConfigurationChange += CurrentSettings_OnConfigurationChange; + + base.CanHandlePowerEvent = true; + + exeLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + Logger.Info("Initializing"); + dataClient.OnClientConnected += Svc_OnClientConnected; + dataClient.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; + dataClient.OnClientDisconnected += Svc_OnClientDisconnected; + dataClient.OnShutdownEvent += Svc_OnShutdownEvent; + dataClient.OnLogLevelEvent += ServiceClient_OnLogLevelEvent; + dataClient.OnNotificationEvent += ServiceClient_OnNotificationEvent; + + svr.CaptureLogs = CaptureLogs; + svr.SetLogLevel = SetLogLevel; + svr.SetReleaseStream = SetReleaseStream; + svr.DoUpdateCheck = DoUpdateCheck; + svr.TriggerUpdate = TriggerUpdate; + svr.SetAutomaticUpdateDisabled = SetAutomaticUpdateDisabled; + svr.SetAutomaticUpdateURL = SetAutomaticUpdateURL; + + string assemblyVersionStr = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? + assemblyVersion = new Version(assemblyVersionStr); + asmDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + updateFolder = Path.Combine(asmDir, "updates"); + if (!Directory.Exists(updateFolder)) { + Directory.CreateDirectory(updateFolder); + } + } + + private SvcResponse SetAutomaticUpdateURL(string url) { + SvcResponse failure = new SvcResponse(); + failure.Code = (int)ErrorCodes.URL_INVALID; + failure.Error = $"The url supplied is invalid: \n{url}\n"; + failure.Message = "Failure"; + + + SvcResponse r = new SvcResponse(); + if (url == null || !url.StartsWith("http")) { + return failure; + } else { + // check the url exists and appears correct... + var check = new GithubCheck(assemblyVersion, url); + + if (check != null) { + var v = check.GetNextVersion(); + + if (v == null) { + return failure; + } + if (v.Revision.ToString().Trim() == "") { + return failure; + } + } + + checkUpdateImmediately(); + + CurrentSettings.AutomaticUpdateURL = url; + CurrentSettings.Write(); + r.Message = "Success"; + } + return r; + } + + private void CurrentSettings_OnConfigurationChange(object sender, ControllerEvent e) { + MonitorServiceStatusEvent evt; + if (lastInstallationNotification != null) { + evt = lastInstallationNotification; + } else { + evt = new MonitorServiceStatusEvent() { + Code = 0, + Error = "", + Message = "Configuration Changed", + Type = "Status", + Status = ServiceActions.ServiceStatus(), + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString(), + AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, + }; + } + Logger.Debug($"notifying consumers of change to CurrentSettings. AutomaticUpdates status = {(CurrentSettings.AutomaticUpdatesDisabled ? "disabled" : "enabled")}"); + EventRegistry.SendEventToConsumers(evt); + } + + private SvcResponse SetAutomaticUpdateDisabled(bool disabled) { + if (lastInstallationNotification != null) { + lastInstallationNotification.AutomaticUpgradeDisabled = disabled.ToString(); + } + CurrentSettings.AutomaticUpdatesDisabled = disabled; + CurrentSettings.Write(); + SvcResponse r = new SvcResponse(); + r.Message = "Success"; + return r; + } + + private SvcResponse TriggerUpdate() { + SvcResponse r = new SvcResponse(); + r.Message = "Initiating Update"; + + Task.Run(() => { installZDE(lastUpdateCheck); }); + return r; + } + + + private void checkUpdateImmediately() { + try { + CheckUpdate(null, null); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error in CheckUpdate"); + } + } + + private StatusCheck DoUpdateCheck() { + StatusCheck r = new StatusCheck(); + + UpdateCheck check = getCheck(assemblyVersion); + + r.Code = check.Avail; + r.ReleaseStream = IsBeta ? "beta" : "stable"; + switch (r.Code) { + case -1: + r.Message = $"An update is available: {check.GetNextVersion()}"; + r.UpdateAvailable = true; + Logger.Debug("Update {0} is published on {1}", check.GetNextVersion(), check.PublishDate); + checkUpdateImmediately(); + break; + case 0: + r.Message = $"The current version [{assemblyVersion}] is the latest"; + break; + case 1: + r.Message = $"Your version [{assemblyVersion}] is newer than the latest release"; + break; + default: + r.Message = "Update check failed"; + break; + } + return r; + } + + private void SetLogLevel(string level) { + try { + Logger.Info("request to change log level received: {0}", level); + if (("" + level).ToLower().Trim() == "verbose") { + level = "trace"; + Logger.Info("request to change log level to verbose - but using trace instead"); + } + var l = LogLevel.FromString(level); + foreach (var rule in LogManager.Configuration.LoggingRules) { + rule.EnableLoggingForLevel(l); + rule.SetLoggingLevels(l, LogLevel.Fatal); + } + + LogManager.ReconfigExistingLoggers(); + Logger.Info("logger reconfigured to log at level: {0}", l); + } catch (Exception e) { + Logger.Error(e, "Could NOT set the log level for loggers??? {0}", e.Message); + } + } + + private void SetReleaseStream(string stream) { + string markerFile = Path.Combine(exeLocation, betaStreamMarkerFile); + if (stream == "beta") { + if (IsBeta) { + Logger.Debug("already using beta stream. No action taken"); + } else { + Logger.Info("Setting update service to use beta stream!"); + using (File.Create(markerFile)) { + + } + AccessUtils.GrantAccessToFile(markerFile); //allow anyone to delete this manually if need be... + Logger.Debug("added marker file: {0}", markerFile); + } + } else { + if (!IsBeta) { + Logger.Debug("already using release stream. No action taken"); + } else { + Logger.Info("Setting update service to use release stream!"); + if (File.Exists(markerFile)) { + File.Delete(markerFile); + Logger.Debug("removed marker file: {0}", markerFile); + } + } + } + } + + private string CaptureLogs() { + try { + string logLocation = Path.Combine(exeLocation, "logs"); + string destinationLocation = Path.Combine(exeLocation, "temp"); + string serviceLogsLocation = Path.Combine(logLocation, "service"); + string serviceLogsDest = Path.Combine(destinationLocation, "service"); + + Logger.Debug("removing leftover temp folder: {0}", destinationLocation); + try { + Directory.Delete(destinationLocation, true); + } catch { + //means it doesn't exist + } + + Directory.CreateDirectory(destinationLocation); + + Logger.Debug("copying all directories from: {0}", logLocation); + foreach (string dirPath in Directory.GetDirectories(logLocation, "*", SearchOption.AllDirectories)) { + Directory.CreateDirectory(dirPath.Replace(logLocation, destinationLocation)); + } + + Logger.Debug("copying all non-zip files from: {0}", logLocation); + foreach (string newPath in Directory.GetFiles(logLocation, "*.*", SearchOption.AllDirectories)) { + if (!newPath.EndsWith(".zip")) { + File.Copy(newPath, newPath.Replace(logLocation, destinationLocation), true); + } + } + + Logger.Debug("copying service files from: {0} to {1}", serviceLogsLocation, serviceLogsDest); + Directory.CreateDirectory(serviceLogsDest); + foreach (string newPath in Directory.GetFiles(serviceLogsLocation, "*.*", SearchOption.TopDirectoryOnly)) { + if (newPath.EndsWith(".log") || newPath.Contains("config.json")) { + Logger.Debug("copying service log: {0}", newPath); + File.Copy(newPath, newPath.Replace(serviceLogsLocation, serviceLogsDest), true); + } + } + + outputIpconfigInfo(destinationLocation); + outputSystemInfo(destinationLocation); + outputDnsCache(destinationLocation); + outputExternalIP(destinationLocation); + outputTasklist(destinationLocation); + outputRouteInfo(destinationLocation); + outputNetstatInfo(destinationLocation); + outputNrpt(destinationLocation); + + Task.Delay(500).Wait(); + + string zipName = Path.Combine(logLocation, DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + ".zip"); + ZipFile.CreateFromDirectory(destinationLocation, zipName); + + Logger.Debug("cleaning up temp folder: {0}", destinationLocation); + try { + Directory.Delete(destinationLocation, true); + } catch { + //means it doesn't exist + } + return zipName; + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error in generating system files {0}", ex.Message); + return null; + } + } + + private void outputIpconfigInfo(string destinationFolder) { + Logger.Info("capturing ipconfig information"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var ipconfigOut = Path.Combine(destinationFolder, "ipconfig.all.txt"); + Logger.Debug("copying ipconfig /all to {0}", ipconfigOut); + startInfo.Arguments = $"/C ipconfig /all > \"{ipconfigOut}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error in outputIpconfigInfo {0}", ex.Message); + } + } + + private void outputSystemInfo(string destinationFolder) { + Logger.Info("capturing systeminfo"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var sysinfoOut = Path.Combine(destinationFolder, "systeminfo.txt"); + Logger.Debug("running systeminfo to {0}", sysinfoOut); + startInfo.Arguments = $"/C systeminfo > \"{sysinfoOut}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error in outputSystemInfo {0}", ex.Message); + } + } + + private void outputDnsCache(string destinationFolder) { + Logger.Info("capturing dns cache information"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var dnsCache = Path.Combine(destinationFolder, "dnsCache.txt"); + Logger.Debug("running ipconfig /displaydns to {0}", dnsCache); + startInfo.Arguments = $"/C ipconfig /displaydns > \"{dnsCache}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error in outputDnsCache {0}", ex.Message); + } + } + + private void outputExternalIP(string destinationFolder) { + Logger.Info("capturing external IP address using nslookup command"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var extIpFile = Path.Combine(destinationFolder, "externalIP.txt"); + Logger.Debug("running nslookup myip.opendns.com. resolver1.opendns.com to {0}", extIpFile); + startInfo.Arguments = $"/C nslookup myip.opendns.com. resolver1.opendns.com > \"{extIpFile}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error in outputExternalIP {0}", ex.Message); + } + } + + private void outputTasklist(string destinationFolder) { + Logger.Info("capturing executing tasks"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var tasklistOutput = Path.Combine(destinationFolder, "tasklist.txt"); + Logger.Debug("running tasklist to {0}", tasklistOutput); + startInfo.Arguments = $"/C tasklist > \"{tasklistOutput}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error {0}", ex.Message); + } + } + + private void outputRouteInfo(string destinationFolder) { + Logger.Info("capturing network routes"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var networkRoutes = Path.Combine(destinationFolder, "network-routes.txt"); + Logger.Debug("running route print to {0}", networkRoutes); + startInfo.Arguments = $"/C route print > \"{networkRoutes}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error {0}", ex.Message); + } + } + + private void outputNetstatInfo(string destinationFolder) { + Logger.Info("capturing netstat"); + try { + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + var netstatOutput = Path.Combine(destinationFolder, "netstat.txt"); + Logger.Debug("running netstat -ano to {0}", netstatOutput); + startInfo.Arguments = $"/C netstat -ano > \"{netstatOutput}\""; + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error {0}", ex.Message); + } + } + + private void outputNrpt(string destinationFolder) { + Logger.Info("outputting NRPT rules"); + try { + Logger.Info("outputting NRPT DnsClientNrptRule"); + string nrptRuleOutput = Path.Combine(destinationFolder, "NrptRule.txt"); + Process nrptRuleProcess = new Process(); + ProcessStartInfo nrptRuleStartInfo = new ProcessStartInfo(); + nrptRuleStartInfo.WindowStyle = ProcessWindowStyle.Hidden; + nrptRuleStartInfo.FileName = "cmd.exe"; + nrptRuleStartInfo.Arguments = $"/C powershell \"Get-DnsClientNrptRule | sort -Property Namespace\" > \"{nrptRuleOutput}\""; + Logger.Info("Running: {0}", nrptRuleStartInfo.Arguments); + nrptRuleProcess.StartInfo = nrptRuleStartInfo; + nrptRuleProcess.Start(); + nrptRuleProcess.WaitForExit(); + + Logger.Info("outputting NRPT DnsClientNrptPolicy"); + string nrptOutput = Path.Combine(destinationFolder, "NrptPolicy.txt"); + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + startInfo.Arguments = $"/C powershell \"Get-DnsClientNrptPolicy | sort -Property Namespace\" > \"{nrptOutput}\""; + Logger.Info("Running: {0}", startInfo.Arguments); + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error {0}", ex.Message); + } + } + + public void Debug() { + OnStart(null);// new string[] { "FilesystemCheck" }); + } + + protected override void OnStart(string[] args) { + Logger.Debug("args: {0}", args); + Logger.Info("ziti-monitor service is starting"); + + var logs = Path.Combine(exeLocation, "logs"); + addLogsFolder(exeLocation); + addLogsFolder(logs); + addLogsFolder(Path.Combine(logs, "UI")); + addLogsFolder(Path.Combine(logs, "ZitiMonitorService")); + addLogsFolder(Path.Combine(logs, "service")); + + AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiUpdateService.exe.config")); //allow anyone to change the config file + AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiUpdateService-log.config")); //allow anyone to change the log file config + AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiDesktopEdge.exe.config")); //allow anyone to change the config file + AccessUtils.GrantAccessToFile(Path.Combine(exeLocation, "ZitiDesktopEdge-log.config")); //allow anyone to change the log file config + + zetHealthcheck.Interval = zetHealthcheckInterval * 1000; + zetHealthcheck.Elapsed += zitiEdgeTunnelAlivenessCheck; + + Logger.Info("starting ipc server"); + ipcServer = svr.startIpcServerAsync(onIpcClientAsync); + Logger.Info("starting events server"); + eventServer = svr.startEventsServerAsync(onEventsClientAsync); + + Logger.Info("starting service watchers"); + if (!running) { + running = true; + Task.Run(() => { + SetupServiceWatchers(); + }); + } + Logger.Info("ziti-monitor service is initialized and running"); + base.OnStart(args); + } + + private void zitiEdgeTunnelAlivenessCheck(object sender, ElapsedEventArgs e) { + try { + if (zetSemaphore.Wait(TimeSpan.FromSeconds(zetHealthcheckInterval))) { + Logger.Trace("ziti-edge-tunnel aliveness check starts"); + dataClient.GetStatusAsync().Wait(); + zetSemaphore.Release(); + Interlocked.Exchange(ref zetFailedCheckCounter, 0); + Logger.Trace("ziti-edge-tunnel aliveness check ends successfully"); + } else { + Interlocked.Add(ref zetFailedCheckCounter, 1); + Logger.Warn("ziti-edge-tunnel aliveness check appears blocked and has been for {} times", zetFailedCheckCounter); + if (zetFailedCheckCounter > 2) { + disableHealthCheck(); + //after 3 failures, just terminate ziti-edge-tunnel + Interlocked.Exchange(ref zetFailedCheckCounter, 0); //reset the counter back to 0 + Logger.Warn("forcefully stopping ziti-edge-tunnel as it has been blocked for too long"); + stopProcessForcefully("ziti-edge-tunnel", "data service [ziti]"); + + Logger.Info("immediately restarting ziti-edge-tunnel"); + ServiceActions.StartService(); //attempt to start the service + } + } + } catch (Exception ex) { + Logger.Error("ziti-edge-tunnel aliveness check ends exceptionally: {}", ex.Message); + Logger.Error(ex); + } + } + + async private Task onEventsClientAsync(StreamWriter writer) { + try { + Logger.Info("a new events client was connected"); + //reset to release stream + //initial status when connecting the event stream + MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { + Code = 0, + Error = "", + Message = "Success", + Type = "Status", + Status = ServiceActions.ServiceStatus(), + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString(), + AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, + }; + await writer.WriteLineAsync(JsonConvert.SerializeObject(status)); + await writer.FlushAsync(); + + //if a new client attaches - send the last update check status + if (lastUpdateCheck != null) { + await writer.WriteLineAsync(JsonConvert.SerializeObject(lastInstallationNotification)); + await writer.FlushAsync(); + } + } catch (Exception ex) { + Logger.Error("UNEXPECTED ERROR: {}", ex); + } + } + +#pragma warning disable 1998 //This async method lacks 'await' + async private Task onIpcClientAsync(StreamWriter writer) { + Logger.Info("a new ipc client was connected"); + } +#pragma warning restore 1998 //This async method lacks 'await' + + private void addLogsFolder(string path) { + if (!Directory.Exists(path)) { + Logger.Info($"creating folder: {path}"); + Directory.CreateDirectory(path); + AccessUtils.GrantAccessToDirectory(path); + } + } + + public void WaitForCompletion() { + Task.WaitAll(ipcServer, eventServer); + } + + protected override void OnStop() { + Logger.Info("ziti-monitor OnStop was called"); + base.OnStop(); + } + + protected override void OnPause() { + Logger.Info("ziti-monitor OnPause was called"); + base.OnPause(); + } + + protected override void OnShutdown() { + Logger.Info("ziti-monitor OnShutdown was called"); + base.OnShutdown(); + } + + protected override void OnContinue() { + Logger.Info("ziti-monitor OnContinue was called"); + base.OnContinue(); + } + + protected override void OnCustomCommand(int command) { + Logger.Info("ziti-monitor OnCustomCommand was called {0}", command); + base.OnCustomCommand(command); + } + + protected override void OnSessionChange(SessionChangeDescription changeDescription) { + Logger.Info("ziti-monitor OnSessionChange was called {0}", changeDescription); + base.OnSessionChange(changeDescription); + } + + protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) { + Logger.Info("ziti-monitor OnPowerEvent was called {0}", powerStatus); + if (powerStatus == PowerBroadcastStatus.Suspend) { + // when going to sleep, make sure the healthcheck is disabled or accounts for going to sleep + disableHealthCheck(); + } + return base.OnPowerEvent(powerStatus); + } + + private void SetupServiceWatchers() { + var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); + var upInt = TimeSpan.Zero; + if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { + upInt = new TimeSpan(0, 10, 0); + } + + if (upInt.TotalMilliseconds < 10 * 60 * 1000) { + Logger.Warn("provided time [{0}] is too small. Using 10 minutes.", updateTimerInterval); +#if MOCKUPDATE || ALLOWFASTINTERVAL + Logger.Info("MOCKUPDATE detected. Not limiting check to 10 minutes"); +#else + upInt = TimeSpan.Parse("0:10:0"); +#endif + } + + _updateTimer = new System.Timers.Timer(); + _updateTimer.Elapsed += CheckUpdate; + _updateTimer.Interval = upInt.TotalMilliseconds; + _updateTimer.Enabled = true; + _updateTimer.Start(); + Logger.Info("Version Checker is running every {0} minutes", upInt.TotalMinutes); + + cleanOldLogs(asmDir); + scanForStaleDownloads(updateFolder); + + checkUpdateImmediately(); + + try { + dataClient.ConnectAsync().Wait(); + } catch { + dataClient.Reconnect(); + } + + dataClient.WaitForConnectionAsync().Wait(); + } + + private void cleanOldLogs(string whereToScan) { + //this function will be removed in the future. it's here to clean out the old ziti-monitor*log files that + //were there before the 1.5.0 release + try { + Logger.Info("Scanning for stale logs"); + foreach (var f in Directory.EnumerateFiles(whereToScan)) { + FileInfo logFile = new FileInfo(f); + if (logFile.Name.StartsWith("ziti-monitor.") && logFile.Name.EndsWith(".log")) { + Logger.Info("removing old log file: " + logFile.Name); + logFile.Delete(); + } + } + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error has occurred"); + } + } + +#if MOCKUPDATE + static DateTime mockDate = DateTime.Now; +#endif + private UpdateCheck getCheck(Version v) { +#if MOCKUPDATE + //run with MOCKUPDATE to enable debugging/mocking the update check + var check = new FilesystemCheck(v, -1, mockDate, "FilesysteCheck.download.mock.txt", new Version("2.1.4")); +#else + + if (string.IsNullOrEmpty(CurrentSettings.AutomaticUpdateURL)) { + CurrentSettings.AutomaticUpdateURL = GithubAPI.ProdUrl; + Logger.Info("Settings does not contain update url. Setting to: {}", CurrentSettings.AutomaticUpdateURL); + CurrentSettings.Write(); + } else { + Logger.Info("Settings contained a value for update url. Using: {}", CurrentSettings.AutomaticUpdateURL); + } + + var check = new GithubCheck(v, CurrentSettings.AutomaticUpdateURL); +#endif + return check; + } + + private InstallationNotificationEvent newInstallationNotificationEvent(string version) { + InstallationNotificationEvent info = new InstallationNotificationEvent() { + Code = 0, + Error = "", + Message = "InstallationUpdate", + Type = "Notification", + Status = ServiceActions.ServiceStatus(), + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString().ToLower(), + AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, + ZDEVersion = version, + }; + return info; + } + + private void CheckUpdate(object sender, ElapsedEventArgs e) { + if (e != null) { + Logger.Debug("Timer triggered CheckUpdate at {0}", e.SignalTime); + } + semaphore.Wait(); + + try { + Logger.Debug("checking for update"); + var check = getCheck(assemblyVersion); + + if (check.Avail >= 0) { + Logger.Debug("update check complete. no update available"); + semaphore.Release(); + return; + } + + Logger.Info("update is available."); + if (!Directory.Exists(updateFolder)) { + Directory.CreateDirectory(updateFolder); + } + InstallationNotificationEvent info = newInstallationNotificationEvent(check.GetNextVersion().ToString()); + info.PublishTime = check.PublishDate; + info.NotificationDuration = InstallationReminder(); + if (InstallationIsCritical(check.PublishDate)) { + info.InstallTime = DateTime.Now + TimeSpan.Parse("0:0:30"); + Logger.Warn("Installation is critical! for ZDE version: {0}. update published at: {1}. approximate install time: {2}", info.ZDEVersion, check.PublishDate, info.InstallTime); + NotifyInstallationUpdates(info, true); + if (CurrentSettings.AutomaticUpdatesDisabled) { + Logger.Debug("AutomaticUpdatesDisabled is set to true. Automatic update is disabled."); + } else { + Thread.Sleep(30); + installZDE(check); + } + } else { + info.InstallTime = InstallDateFromPublishDate(check.PublishDate); + Logger.Info("Installation reminder for ZDE version: {0}. update published at: {1}. approximate install time: {2}", info.ZDEVersion, check.PublishDate, info.InstallTime); + NotifyInstallationUpdates(info); + } + lastUpdateCheck = check; + lastInstallationNotification = info; + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error has occurred during the check for ZDE updates"); + } + semaphore.Release(); + } + + private void installZDE(UpdateCheck check) { + string fileDestination = Path.Combine(updateFolder, check?.FileName); + + if (check.AlreadyDownloaded(updateFolder, check.FileName)) { + Logger.Trace("package has already been downloaded to {0}", fileDestination); + } else { + Logger.Info("copying update package begins"); + check.CopyUpdatePackage(updateFolder, check.FileName); + Logger.Info("copying update package complete"); + } + + Logger.Info("package is in {0} - moving to install phase", fileDestination); + + if (!check.HashIsValid(updateFolder, check.FileName)) { + Logger.Warn("The file was downloaded but the hash is not valid. The file will be removed: {0}", fileDestination); + File.Delete(fileDestination); + return; + } + Logger.Debug("downloaded file hash was correct. update can continue."); +#if !SKIPUPDATE + try { + Logger.Info("verifying file [{}]", fileDestination); + new SignedFileValidator(fileDestination).Verify(); + Logger.Info("SignedFileValidator complete"); + + StopZiti(); + StopUI().Wait(); + + Logger.Info("Running update package: " + fileDestination); + // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well + Process.Start(fileDestination, "/passive"); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error during installation"); + } +#else + Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE of version: {} published at {}", check.GetNextVersion(), check.PublishDate); + Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE of version: {} published at {}", check.GetNextVersion(), check.PublishDate); + Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE of version: {} published at {}", check.GetNextVersion(), check.PublishDate); +#endif + } + + private bool isOlder(Version current) { + int compare = current.CompareTo(assemblyVersion); + Logger.Info("comparing current[{0}] to compare[{1}]: {2}", current.ToString(), assemblyVersion.ToString(), compare); + if (compare < 0) { + return true; + } else if (compare > 0) { + return false; + } else { + return false; + } + } + + private void scanForStaleDownloads(string folder) { + try { + if (!Directory.Exists(folder)) { + Logger.Debug("folder {0} does not exist. skipping", folder); + return; + } + Logger.Info("Scanning for stale downloads"); + foreach (var f in Directory.EnumerateFiles(folder)) { + try { + FileInfo fi = new FileInfo(f); + if (fi.Exists) { + if (fi.Name.StartsWith(filePrefix)) { + Logger.Debug("scanning for staleness: " + f); + string ver = Path.GetFileNameWithoutExtension(f).Substring(filePrefix.Length); + Version fileVersion = Version.Parse(ver); + if (isOlder(fileVersion)) { + Logger.Info("Removing old download: " + fi.Name); + fi.Delete(); + } else { + Logger.Debug("Retaining file. {1} is the same or newer than {1}", fi.Name, assemblyVersion); + } + } else { + Logger.Debug("skipping file named {0}", f); + } + } else { + Logger.Debug("file named {0} did not exist?", f); + } + } catch (Exception ex) { + Logger.Error(ex, "Unexpected exception processing {0}", f); + } + } + } catch (Exception ex) { + Logger.Error(ex, "Unexpected exception"); + } + } + + private void StopZiti() { + Logger.Info("Stopping the ziti service..."); + controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); + bool cleanStop = false; + if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { + try { + controller.Stop(); + Logger.Debug("Waiting for the ziti service to stop."); + controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); + Logger.Debug("The ziti service was stopped successfully."); + cleanStop = true; + } catch (Exception e) { + Logger.Error(e, "Timout while trying to stop service!"); + } + } else { + Logger.Debug("The ziti has ALREADY been stopped successfully."); + } + if (!cleanStop) { + Logger.Debug("Stopping ziti-edge-tunnel forcefully."); + stopProcessForcefully("ziti-edge-tunnel", "data service [ziti]"); + } + } + + private void stopProcessForcefully(string processName, string description) { + try { + Logger.Info("Closing the {description} process", description); + Process[] workers = Process.GetProcessesByName(processName); + if (workers.Length < 1) { + Logger.Info("No {description} process found to close.", description); + return; + } + // though strange, because we're about to kill the process, this is still + // considered 'expected' since the monitor service is shutting it down (forcefully). + // not clean is to indicate the process ended unexpectedly + dataClient.ExpectedShutdown = true; + + foreach (Process worker in workers) { + try { + Logger.Info("Killing: {0}", worker); + if (!worker.CloseMainWindow()) { + //don't care right now because when called on the UI it just gets 'hidden' + } + worker.Kill(); + worker.WaitForExit(5000); + Logger.Info("Stopping the {description} process killed", description); + worker.Dispose(); + } catch (Exception e) { + Logger.Error(e, "Unexpected error when closing the {description}!", description); + } + } + } catch (Exception e) { + Logger.Error(e, "Unexpected error when closing the {description}!", description); + } + } + + async private Task StopUI() { + //first try to ask the UI to exit: + + MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { + Code = 0, + Error = "", + Message = "Upgrading" + }; + EventRegistry.SendEventToConsumers(status); + + await Task.Delay(1000); //wait for the event to send and give the UI time to close... + + stopProcessForcefully("ZitiDesktopEdge", "UI"); + } + + private static void Svc_OnShutdownEvent(object sender, StatusEvent e) { + Logger.Info("the service is shutting down normally..."); + + MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { + Code = 0, + Error = "SERVICE DOWN", + Message = "SERVICE DOWN", + Status = ServiceActions.ServiceStatus() + }; + EventRegistry.SendEventToConsumers(status); + } + + private void ServiceClient_OnLogLevelEvent(object sender, LogLevelEvent e) { + SetLogLevel(e.LogLevel); + } + + private void ServiceClient_OnNotificationEvent(object sender, NotificationEvent e) { + Logger.Trace("Notification event but not acting: {0}", e.Op); + } + + private void Svc_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { + string dns = e?.Status?.IpInfo?.DNS; + string version = e?.Status?.ServiceVersion.Version; + string op = e?.Op; + Logger.Info($"Operation {op}. running dns: {dns} at version {version}"); + + SetLogLevel(e.Status.LogLevel); + } + + private void disableHealthCheck() { + if (zetHealthcheck.Enabled) { + zetHealthcheck.Enabled = true; + zetHealthcheck.Stop(); + Logger.Info("ziti-edge-tunnel health check disabled"); + } else { + Logger.Info("ziti-edge-tunnel health check already disabled"); + } + } + + private void enableHealthCheck() { + if (!zetHealthcheck.Enabled) { + zetHealthcheck.Enabled = false; + zetHealthcheck.Start(); + Logger.Info("ziti-edge-tunnel health check enabled"); + } else { + Logger.Info("ziti-edge-tunnel health check already enabled"); + } + } + + private void Svc_OnClientConnected(object sender, object e) { + Logger.Info("successfully connected to service"); + enableHealthCheck(); + } + + private void Svc_OnClientDisconnected(object sender, object e) { + disableHealthCheck(); //no need to healthcheck when we know it's disconnected + DataClient svc = (DataClient)sender; + if (svc.ExpectedShutdown) { + //then this is fine and expected - the service is shutting down + Logger.Info("client disconnected due to clean service shutdown"); + } else { + Logger.Error("SERVICE IS DOWN and did not exit cleanly."); + + MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { + Code = 10, + Error = "SERVICE DOWN", + Message = "SERVICE DOWN", + Type = "Status", + Status = ServiceActions.ServiceStatus(), + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = CurrentSettings.AutomaticUpdatesDisabled.ToString(), + AutomaticUpgradeURL = CurrentSettings.AutomaticUpdateURL, + }; + EventRegistry.SendEventToConsumers(status); + } + } + + private static void EnumerateDNS() { + var ps = System.Management.Automation.PowerShell.Create(); + ps.AddScript("Get-DnsClientServerAddress"); + var results = ps.Invoke(); + + using (StringWriter sw = new StringWriter()) { + foreach (var r in results) { + string name = (string)r.Properties["InterfaceAlias"].Value; + string[] dnses = (string[])r.Properties["ServerAddresses"].Value; + sw.WriteLine($"Interface: {name}. DNS: {string.Join(",", dnses)}"); + } + Logger.Info("DNS RESULTS:\n{0}", sw.ToString()); + } + } + + private TimeSpan InstallationReminder() { + var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationReminder"); + var reminderInt = TimeSpan.Zero; + if (!TimeSpan.TryParse(installationReminderIntervalStr, out reminderInt)) { + reminderInt = new TimeSpan(0, 1, 0); + } + return reminderInt; + } + + private DateTime InstallDateFromPublishDate(DateTime publishDate) { + var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationCritical"); + var instCritTimespan = TimeSpan.Zero; + if (!TimeSpan.TryParse(installationReminderIntervalStr, out instCritTimespan)) { + instCritTimespan = TimeSpan.Parse("7:0:0:0"); + } + return publishDate + instCritTimespan; + } + + private bool InstallationIsCritical(DateTime publishDate) { + var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationCritical"); + var instCritTimespan = TimeSpan.Zero; + if (!TimeSpan.TryParse(installationReminderIntervalStr, out instCritTimespan)) { + instCritTimespan = TimeSpan.Parse("7:0:0:0"); + } + return DateTime.Now > publishDate + instCritTimespan; + } + + private void NotifyInstallationUpdates(InstallationNotificationEvent evt) { + NotifyInstallationUpdates(evt, false); + } + + private void NotifyInstallationUpdates(InstallationNotificationEvent evt, bool force) { + try { + evt.Message = "InstallationUpdate"; + evt.Type = "Notification"; + EventRegistry.SendEventToConsumers(evt); + Logger.Debug("NotifyInstallationUpdates: sent for version {0} is sent to the events pipe...", evt.ZDEVersion); + return; + } catch (Exception e) { + Logger.Error("The notification for the installation updates for version {0} has failed: {1}", evt.ZDEVersion, e); + } + } + } +} diff --git a/adv-inst-version b/adv-inst-version index b798090d..70a91e23 100644 --- a/adv-inst-version +++ b/adv-inst-version @@ -1 +1 @@ -21.8.2 +22.1 diff --git a/build-test-release.ps1 b/build-test-release.ps1 index c4d6d9ac..150fe63d 100644 --- a/build-test-release.ps1 +++ b/build-test-release.ps1 @@ -6,7 +6,6 @@ # .\build-test-release.ps1 -version 1.2.3 -url https://lnxiskqx49x4.share.zrok.io/local -stream "dev" -published_at (Get-Date) # .\build-test-release.ps1 -version 1.2.3 -url https://lnxiskqx49x4.share.zrok.io/local -stream "dev" -published_at "2023-11-02T14:30:00" param( - [Parameter(Mandatory = $true)] [string]$version, [string]$url = "http://localhost:8000/release-streams/local", [string]$stream = "local", @@ -18,6 +17,44 @@ param( echo "" $scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Path -Parent +$version = $version.Trim() +if (-not $version) { + if (Test-Path -Path "version") { + $version = (Get-Content -Path "version" -Raw).Trim() + Write-Host -NoNewline "Version not supplied. Using version from file and incrementing: " + Write-Host -ForegroundColor Yellow "${version}" + + # Increment the last tuple + $versionWithoutPrefix = $version -replace '^v', '' + $segments = $versionWithoutPrefix -split '\.' + $segments[-1] = [int]$segments[-1] + 1 + $version = ($segments -join '.') + + Write-Host -NoNewline "New Version: " + Write-Host -ForegroundColor Green "$version" + # Check if the 'version' file has changes in git + $gitStatus = git status --porcelain "version" + + if (-not $gitStatus) { + # File has not been modified in git, proceed to update it + Set-Content -Path "version" -Value $version -NoNewline + } else { + Write-Host "The version file has changes in git. Update skipped." -ForegroundColor Yellow + } + } else { + Write-Host "Version file not found" -ForegroundColor Red + exit 1 + } +} else { + # Regex to match semantic versioning pattern + if ($version -notmatch '^v?\d+(\.\d+){0,3}$') { + Write-Host -ForegroundColor Red "Invalid version format [${version}]. Expected a semantic version (e.g., 1.0.0)." + exit 1 + } + Write-Host -NoNewline "Version: " + Write-Host -ForegroundColor Green "$version" +} + $outputPath = "$scriptDirectory\release-streams\${version}.json" & .\Installer\output-build-json.ps1 -version $version -url $url -stream $stream -published_at $published_at -outputPath $outputPath @@ -28,7 +65,8 @@ if(! $jsonOnly) { & .\Installer\build.ps1 -version $version -url $url -stream $stream -published_at $published_at -jsonOnly $jsonOnly -revertGitAfter $revertGitAfter -versionQualifier $versionQualifier $exitCode = $LASTEXITCODE if($exitCode -gt 0) { - Write-Host "build.ps1 failed!" + Write-Host -ForegroundColor Red "ERROR:" + Write-Host -ForegroundColor Red " - build.ps1 failed!" exit $exitCode } @@ -56,4 +94,4 @@ Write-Host "" Write-Host " python -m http.server 8000" Write-Host "" Write-Host "Set the automatic upgrade url to http://localhost:8000/release-streams/local.json" -Write-Host "" \ No newline at end of file +Write-Host "" diff --git a/manual-testing.md b/manual-testing.md new file mode 100644 index 00000000..95766d85 --- /dev/null +++ b/manual-testing.md @@ -0,0 +1,28 @@ +# Manual Testing + +These are the tests you need to make sure to perform per release. + +## Test Cases + +- Identity with dial access to a single service +- Identity with bind access to a single service +- Identity with dial access to a service with by TOTP posture check +- Identity with dial access to a service with by TOTP + time-based posture check +- Identity with dial access to a service with by TOTP + on-wake locking posture check + +- Identity with dial access to a service with process-based posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check +- Identity with dial access to a service with by posture check + + +- Identity marked as disabled, is disabled on restart of zet +- Identity marked as enabled is enabled on restart +- Identity with overlapping service definitions in two different networks +- +- Multiple ziti edge tunnels running at one time, intercepts are removed on clean shutdown \ No newline at end of file diff --git a/release-notes.md b/release-notes.md index d976cd10..7ff5b52a 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,37 @@ +# Release 2.5.0.13 + +## What's New +* nothing yet + +## Other changes +* none + +## Bugs fixed: +* none + +## Dependency Updates + +* ziti-tunneler: v1.2.2 +* ziti-sdk: 1.1.2 +* tlsuv: v0.32.6[OpenSSL 3.3.1 4 Jun 2024] + +# Release 2.5.0.12 + +## What's New +* OIDC enabled, implementation (coming soon) +* Keychain integration and TPM enablemed, implementation (coming soon) + +## Other changes: +* n/a + +## Bugs fixed: +* n/a + +## Dependency Updates +ziti-tunneler: v2.0.0-alpha24.11 +ziti-sdk: 2.0.0-alpha29 +tlsuv: v0.32.2.1[OpenSSL 3.3.1 4 Jun 2024] + # Release 2.5.0.10 & 2.5.0.11 ## What's New diff --git a/release-streams/beta.json b/release-streams/beta.json index da74e2e3..b868976e 100644 --- a/release-streams/beta.json +++ b/release-streams/beta.json @@ -1,12 +1,12 @@ { - "name": "2.4.1.0", - "tag_name": "2.4.1.0", - "published_at": "2024-09-23T05:50:16Z", + "name": "2.5.0.13", + "tag_name": "2.5.0.13", + "published_at": "2024-10-18T08:32:49Z", "installation_critical": false, "assets": [ { - "name": "Ziti.Desktop.Edge.Client-2.4.1.0.exe", - "browser_download_url": "https://github.com/openziti/desktop-edge-win/releases/download/2.4.1.0/Ziti.Desktop.Edge.Client-2.4.1.0.exe" + "name": "Ziti.Desktop.Edge.Client-2.5.0.13.exe", + "browser_download_url": "https://github.com/openziti/desktop-edge-win/releases/download/2.5.0.13/Ziti.Desktop.Edge.Client-2.5.0.13.exe" } ] } diff --git a/update-versions.ps1 b/update-versions.ps1 index 7dc7d3c6..71a17baf 100644 --- a/update-versions.ps1 +++ b/update-versions.ps1 @@ -1,3 +1,7 @@ +param( + [string]$version +) + function NormalizeVersion([System.Version] $v) { $major = $v.Major $minor = $v.Minor @@ -15,13 +19,17 @@ function NormalizeVersion([System.Version] $v) { } echo "==================================== update-versions.ps1 begins ====================================" -echo "Obtaining version information from .\version" -#$rawVersion=(Get-Content -Path .\version) -$installerVersion=(Get-Content -Path ${scriptPath}\..\version) -if($null -ne $env:ZITI_DESKTOP_EDGE_VERSION) { - echo "ZITI_DESKTOP_EDGE_VERSION is set. Using that: ${env:ZITI_DESKTOP_EDGE_VERSION} instead of version found in file ${installerVersion}" - $installerVersion=$env:ZITI_DESKTOP_EDGE_VERSION - echo "Version set to: ${installerVersion}" +if(-not $version.Trim()) { + echo "Obtaining version information from .\version" + #$rawVersion=(Get-Content -Path .\version) + $installerVersion=(Get-Content -Path ${scriptPath}\..\version) + if($null -ne $env:ZITI_DESKTOP_EDGE_VERSION) { + echo "ZITI_DESKTOP_EDGE_VERSION is set. Using that: ${env:ZITI_DESKTOP_EDGE_VERSION} instead of version found in file ${installerVersion}" + $installerVersion=$env:ZITI_DESKTOP_EDGE_VERSION + echo "Version set to: ${installerVersion}" + } +} else { + $installerVersion = $version } $v=NormalizeVersion($installerVersion) diff --git a/version b/version index b83a6ba5..c4f828de 100644 --- a/version +++ b/version @@ -1 +1 @@ -2.5.0.11 +2.5.0.13