view ServerMonitor/Forms/ServerForm.cs @ 5:b6fe203af9d5

Private key passwords and validation
author Brad Greco <brad@bgreco.net>
date Thu, 28 Feb 2019 21:19:32 -0500
parents 3142e52cbe69
children 052aa62cb42a
line wrap: on
line source

´╗┐using Renci.SshNet;
using Renci.SshNet.Common;
using ServerMonitorApp.Properties;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ServerMonitorApp
{
    public partial class ServerForm : Form
    {
        private bool isNewServer;
        private bool changedPassword;
        private DateTime lastSaveTime;
        private ServerMonitor monitor;
        private BindingList<CheckResult> logResults, logResultsFiltered;
        private CheckStatus[] filteredStatuses;
        private readonly Dictionary<int, CheckForm> checkForms = new Dictionary<int, CheckForm>();
        private readonly Dictionary<CheckBox, CheckStatus> filterChecks;

        public Server Server { get; private set; }

        private IEnumerable<Check> SelectedChecks => CheckGrid.SelectedRows.Cast<DataGridViewRow>().Select(r => r.DataBoundItem).Cast<Check>();

        private Check SelectedCheck => SelectedChecks.FirstOrDefault();

        public ServerForm(ServerMonitor monitor, Server server, bool isNewServer = false)
        {
            InitializeComponent();
            this.monitor = monitor;
            this.isNewServer = isNewServer;
            Server = server;
            filterChecks = new Dictionary<CheckBox, CheckStatus>
            {
                { LogSuccessCheckBox, CheckStatus.Success },
                { LogInformationCheckBox, CheckStatus.Information },
                { LogWarningCheckBox, CheckStatus.Warning },
                { LogErrorCheckBox, CheckStatus.Error },
            };
        }

        private void ServerForm_Load(object sender, EventArgs e)
        {
            CheckBindingSource.DataSource = Server.Checks;
            monitor.CheckStatusChanged += Monitor_CheckStatusChanged;
            CheckGrid.ClearSelection();
            if (isNewServer)
            {
                LoginComboBox.SelectedIndex = 0;
            }
            else
            {
                SetTitle();
                NameTextBox.Text = Server.Name;
                HostTextBox.Text = Server.Host;
                PortTextBox.Text = Server.Port.ToString();
                UsernameTextBox.Text = Server.Username;
                PasswordTextBox.Text = "********************";
                LoginComboBox.SelectedIndex = (int)Server.LoginType;
                KeyTextBox.Text = Server.KeyFile;
                changedPassword = false;
            }

            BindChangeListeners();
            FormatImageButtons();
            UpdateCheckButtons();
        }

        public void Show(bool activate)
        {
            if (!activate)
                WindowState = FormWindowState.Minimized;
            Show();
        }

        private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e)
        {
            if (e.Check.Server != Server)
                return;
            CheckGrid.Refresh();
            if (e.CheckResult != null && logResults != null)
            {
                logResults.Insert(0, e.CheckResult);
                if (logResultsFiltered != null && MatchesFilter(e.CheckResult))
                    logResultsFiltered.Insert(0, e.CheckResult);
            }
        }

        /// <summary>Update the server with the current input values</summary>
        /// <param name="forceSave">
        /// If true, immediately update the config file on disk.
        /// If false, only update the config file if it has not been recently updated.
        /// </param>
        private void UpdateServer(bool forceSave = true)
        {
            Server.Name = NameTextBox.Text;
            Server.Host = HostTextBox.Text.Trim();
            Server.Port = int.TryParse(PortTextBox.Text, out int port) ? port : 0;
            Server.Username = UsernameTextBox.Text.Trim();
            Server.LoginType = (LoginType)LoginComboBox.SelectedIndex;
            Server.KeyFile = KeyTextBox.Text.Trim();
            if (changedPassword)
                Server.Password = PasswordTextBox.Text;

            if (forceSave || lastSaveTime < DateTime.Now.AddSeconds(-5))
            {
                lastSaveTime = DateTime.Now;
                monitor.SaveServers();
            }
        }

        private void SetTitle(string title = null)
        {
            title = (title ?? Server.Name) + (Server.Enabled ? "" : " (disabled)");
            Text = title;
            TitleLabel.Text = title;
        }

        private void NameTextBox_TextChanged(object sender, EventArgs e)
        {
            SetTitle(NameTextBox.Text);
        }

        private void LoginComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (LoginComboBox.SelectedIndex == (int)LoginType.PrivateKey)
            {
                PasswordTextBox.Visible = false;
                KeyTextBox.Visible = true;
                KeyBrowseButton.Visible = true;
            }
            else
            {
                PasswordTextBox.Visible = true;
                KeyTextBox.Visible = false;
                KeyBrowseButton.Visible = false;
            }
        }

        private void NewCheckButton_Click(object sender, EventArgs e)
        {
            ShowCheckForm(null);
        }

        private void EditCheckButton_Click(object sender, EventArgs e)
        {
            EditSelectedCheck();
        }

        private void RunButton_Click(object sender, EventArgs e)
        {
            ExecuteChecks(SelectedChecks);
        }

        private void RunAllButton_Click(object sender, EventArgs e)
        {
            ExecuteChecks(Server.Checks);
        }

        private void ShowCheckForm(Check check)
        {
            if (check != null)
            {
                if (!checkForms.TryGetValue(check.Id, out CheckForm form))
                {
                    form = new CheckForm(monitor, check);
                    checkForms[check.Id] = form;
                    form.FormClosing += CheckForm_FormClosing;
                    form.Show();
                }
                else
                {
                    form.Activate();
                }
            }
            else
            {
                new CheckForm(monitor, Server).Show();
            }
        }

        private void EditSelectedCheck()
        {
            ShowCheckForm(SelectedCheck);
        }

        private void DeleteSelectedChecks()
        {
            if (Settings.Default.ConfirmDeleteCheck)
            {
                string message = "Delete " + (SelectedChecks.Count() == 1 ? "\"" + SelectedCheck.Name + "\"" : "selected checks") + "?";
                using (CheckBoxDialog dialog = new CheckBoxDialog { Message = message })
                {
                    DialogResult result = dialog.ShowDialog();
                    if (dialog.Checked && result == DialogResult.OK)
                    {
                        Settings.Default.ConfirmDeleteCheck = false;
                        Settings.Default.Save();
                    }
                    if (result != DialogResult.OK)
                        return;
                }
            }
            foreach (Check check in SelectedChecks)
                Server.DeleteCheck(check);
        }

        private async void ExecuteChecks(IEnumerable<Check> checks)
        {
            await Task.WhenAll(checks.Select(c => monitor.ExecuteCheckAsync(c)));
        }

        public void ShowLog(Check check)
        {
            CheckTabControl.SelectedTab = LogTabPage;
            LogCheckComboBox.SelectedItem = check;
        }

        private void UpdateCheckButtons()
        {
            RunAllButton.Enabled = CheckGrid.RowCount > 0;
            RunButton.Enabled = DeleteCheckButton.Enabled = CheckGrid.SelectedRows.Count > 0;
            EditCheckButton.Enabled = CheckGrid.SelectedRows.Count == 1;
        }

        private void FormatImageButtons()
        {
            Button[] buttons = new Button[] { NewCheckButton, RunAllButton, RunButton, EditCheckButton, DeleteCheckButton };
            foreach (Button button in buttons)
                Helpers.FormatImageButton(button);
        }

        private void BindChangeListeners()
        {
            Server.EnabledChanged += Server_EnabledChanged;
            foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is TextBox))
                control.TextChanged += (sender, e) => UpdateServer(false);
            foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is ComboBox))
                control.TextChanged += (sender, e) => UpdateServer();
            foreach (CheckBox control in LogTabPage.Controls.OfType<CheckBox>())
                control.CheckedChanged += FilterChanged;
        }

        private void CheckForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            CheckForm form = (CheckForm)sender;
            form.FormClosing -= CheckForm_FormClosing;
            checkForms.Remove(form.CheckId);
            if (form.DialogResult == DialogResult.OK)
                CheckGrid.Refresh();
        }

        private void CheckGrid_SelectionChanged(object sender, EventArgs e)
        {
            UpdateCheckButtons();
        }

        private void PasswordTextBox_TextChanged(object sender, EventArgs e)
        {
            changedPassword = true;
        }

        private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            UpdateServer();
        }

        private void CheckGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            EditSelectedCheck();
        }

        private void CheckGrid_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                EditSelectedCheck();
                e.Handled = true;
            }
        }

        private void DeleteCheckButton_Click(object sender, EventArgs e)
        {
            DeleteSelectedChecks();
            UpdateServer();
        }

        private void CheckGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            if (e.ColumnIndex == StatusColumn.Index)
            {
                e.Value = ((CheckStatus)e.Value).GetIcon();
            }
        }

        private void LogGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            if (e.ColumnIndex == LogStatusColumn.Index)
            {
                e.Value = ((CheckStatus)e.Value).GetIcon();
            }
        }

        private void CheckBindingSource_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (Server?.Checks != null)
            {
                //TODO this might result in the selection being reset to all (if a new check is added, for example.) Restore selected index.
                LogCheckComboBox.Items.Clear();
                LogCheckComboBox.Items.Add("(All)");
                LogCheckComboBox.Items.AddRange(Server.Checks.ToArray());
                LogCheckComboBox.SelectedIndex = 0;
            }
        }

        private void CheckTabControl_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (logResults == null && CheckTabControl.SelectedTab == LogTabPage)
            {
                logResults = new BindingList<CheckResult>(monitor.GetLog(Server));
                LogGrid.DataSource = logResults;
            }
        }

        private void CheckGrid_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == StatusColumn.Index)
                CheckGrid.Cursor = Cursors.Hand;
        }

        private void CheckGrid_CellMouseLeave(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == StatusColumn.Index)
                CheckGrid.Cursor = Cursors.Default;
        }

        private void CheckGrid_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == StatusColumn.Index)
                ShowLog((Check)CheckBindingSource[e.RowIndex]);
        }

        private void CheckGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            // The status column is set to read-only, and updates are manually done here.
            // Otherwise, the change doesn't take effect until the cell loses focus.
            if (e.ColumnIndex == EnabledColumn.Index)
            {
                Check check = (Check)CheckBindingSource[e.RowIndex];
                check.Enabled = !(bool)CheckGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].Value; ;
                Server.UpdateCheck(check);
            }
        }

        private void FilterChanged(object sender, EventArgs e)
        {
            filteredStatuses = filterChecks.Where(fc => fc.Key.Checked).Select(fc => fc.Value).ToArray();
            if (filteredStatuses.Length == filterChecks.Count && LogCheckComboBox.SelectedIndex == 0) {
                LogGrid.DataSource = logResults;
                logResultsFiltered = null;
            }
            else
            {
                logResultsFiltered = new BindingList<CheckResult>(logResults.Where(result => MatchesFilter(result)).ToList());
                LogGrid.DataSource = logResultsFiltered;
            }
        }

        private void ServerForm_Activated(object sender, EventArgs e)
        {
            Win32Helpers.StopFlashWindowEx(this);
        }

        private void KeyBrowseButton_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog() { Title = "Select private key file" };
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                KeyTextBox.Text = dialog.FileName;
                UpdateServer();
            }
        }

        private bool MatchesFilter(CheckResult result)
        {
            return filteredStatuses.Contains(result.CheckStatus) && (LogCheckComboBox.SelectedIndex == 0 || LogCheckComboBox.SelectedItem == result.Check);
        }

        private void KeyTextBox_Leave(object sender, EventArgs e)
        {
            OpenPrivateKey(monitor, Server, this);
        }

        public static void OpenPrivateKey(ServerMonitor monitor, Server server, IWin32Window owner)
        {
            if (server.LoginType != LoginType.PrivateKey)
                return;

            KeyStatus keyStatus = monitor.OpenPrivateKey(server.KeyFile);
            if (keyStatus == KeyStatus.NeedPassword)
            {
                string message = "Private key password for " + server.Name + ":";
                Icon icon = SystemIcons.Question;
                while (keyStatus != KeyStatus.Open)
                {
                    string password = InputDialog.ShowDialog(message, icon, owner);
                    if (password == null)
                        return;
                    keyStatus = monitor.OpenPrivateKey(server.KeyFile, password);
                    if (keyStatus == KeyStatus.NeedPassword)
                    {
                        message = "Incorrect private key password for " + server.Name + ", please try again:";
                        icon = SystemIcons.Error;
                    }
                }
            }
            else if (keyStatus == KeyStatus.NotAccessible)
            {
                MessageBox.Show("The private key file " + server.KeyFile + " is not accessible or does not exist.", "Cannot open private key", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
        }

        private void Server_EnabledChanged(object sender, EventArgs e)
        {
            SetTitle();
        }
    }
}