mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-25 14:27:26 +01:00
92f0499911
We observed that some threads can reach deadlock-ish state after they have Invoked into a control's UI thread. When it happens they are all in a waiting for join or in sleep state for very long time, although there should not be any deadlock situations.
It seems this has something to do with multiple parent controls and with which control we invoked on. This should not make a difference, because we have got one UI thread (for MainWindow) they should wait for, but we have seen it does.
The solution that fixed this issue was to invoke on the MainWindow instead of various controls (see a4fe507adf
).
This changeset is changing all our Invokes to invoke into MainWindow
instead of a control itself. (MainWindow's UI thread is the only UI thread
all Control is using in XenCenter)
This changeset should be in place until we have found the root cause or the exact reason for the above.
1187 lines
51 KiB
C#
1187 lines
51 KiB
C#
/* Copyright (c) Citrix Systems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using XenAPI;
|
|
using XenAdmin.Dialogs;
|
|
using System.Threading;
|
|
using XenAdmin.Controls.DataGridViewEx;
|
|
using XenAdmin.Core;
|
|
using XenAdmin.Actions;
|
|
using XenAdmin.Properties;
|
|
|
|
namespace XenAdmin.TabPages
|
|
{
|
|
public partial class AdPage : BaseTabPage
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
/// <summary>
|
|
/// The pool this settings page refers to (can be a poolOfOne). Only set on the GUI thread, and we also assert that it is only
|
|
/// ever set once.
|
|
/// </summary>
|
|
private Pool pool;
|
|
private Host master;
|
|
private ActionProgressDialog removeUserDialog;
|
|
private ResolvingSubjectsDialog resolvingSubjectsDialog;
|
|
private Thread _loggedInStatusUpdater;
|
|
/// <summary>
|
|
/// We keep a reference to this prompt to make repeated attempts to enable AD more user friendly (remembering the previously tried creds)
|
|
/// </summary>
|
|
private AdPasswordPrompt joinPrompt;
|
|
|
|
private IXenObject _xenObject;
|
|
private readonly CollectionChangeEventHandler Pool_CollectionChangedWithInvoke;
|
|
public IXenObject XenObject
|
|
{
|
|
set
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (_xenObject != null)
|
|
{
|
|
ClearHandles();
|
|
}
|
|
|
|
_xenObject = value;
|
|
|
|
if (_xenObject == null)
|
|
return;
|
|
|
|
SetRbacVisible(Helpers.MidnightRideOrGreater(_xenObject.Connection));
|
|
pool = Helpers.GetPoolOfOne(_xenObject.Connection);
|
|
if (pool == null)
|
|
{
|
|
// Cache not populated
|
|
_xenObject.Connection.Cache.RegisterCollectionChanged<Pool>(Pool_CollectionChangedWithInvoke);
|
|
return;
|
|
}
|
|
|
|
pool.PropertyChanged += new PropertyChangedEventHandler(pool_PropertyChanged);
|
|
pool.Connection.Session.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Session_PropertyChanged);
|
|
RefreshMaster();
|
|
|
|
if (_loggedInStatusUpdater == null)
|
|
{
|
|
// Fire off the the thread that will update the logged in status
|
|
_loggedInStatusUpdater = new Thread(updateLoggedInStatus);
|
|
_loggedInStatusUpdater.IsBackground = true;
|
|
_loggedInStatusUpdater.Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
public AdPage()
|
|
{
|
|
InitializeComponent();
|
|
Pool_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(Pool_CollectionChanged);
|
|
ColumnSubject.CellTemplate = new KeyValuePairCell();
|
|
tTipLogoutButton.SetToolTip(Messages.AD_CANNOT_MODIFY_ROOT);
|
|
tTipRemoveButton.SetToolTip(Messages.AD_CANNOT_MODIFY_ROOT);
|
|
ConnectionsManager.History.CollectionChanged += new CollectionChangeEventHandler(History_CollectionChanged);
|
|
Text = Messages.ACTIVE_DIRECTORY_TAB_TITLE;
|
|
joinPrompt = new AdPasswordPrompt(true, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method is used when the cache was not populated by the time we set the XenObject. It sets the appropriate event handlers,
|
|
/// references to the master and the pool, and populates the tab with the correct configuration. It de-registers
|
|
/// itself when successful.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
void Pool_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
pool = Helpers.GetPoolOfOne(_xenObject.Connection);
|
|
|
|
if (pool != null)
|
|
_xenObject.Connection.Cache.DeregisterCollectionChanged<Pool>(Pool_CollectionChangedWithInvoke);
|
|
else
|
|
return;
|
|
|
|
pool.PropertyChanged += new PropertyChangedEventHandler(pool_PropertyChanged);
|
|
pool.Connection.Session.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Session_PropertyChanged);
|
|
RefreshMaster();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the references to the pool master and updates the tab with the correct configuration. Remember to de-reference events
|
|
/// on the old master before running this method.
|
|
/// </summary>
|
|
private void RefreshMaster()
|
|
{
|
|
master = _xenObject.Connection.Resolve(pool.master);
|
|
master.PropertyChanged += new PropertyChangedEventHandler(master_PropertyChanged);
|
|
Program.BeginInvoke(this,checkAdType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all event handles that could be set in this control.
|
|
/// </summary>
|
|
private void ClearHandles()
|
|
{
|
|
pool.Connection.Cache.DeregisterBatchCollectionChanged<Subject>(SubjectCollectionChanged);
|
|
pool.PropertyChanged -= new PropertyChangedEventHandler(pool_PropertyChanged);
|
|
if (master != null)
|
|
master.PropertyChanged -= new PropertyChangedEventHandler(master_PropertyChanged);
|
|
|
|
if (removeUserDialog != null)
|
|
removeUserDialog.Dispose();
|
|
|
|
if (resolvingSubjectsDialog != null)
|
|
resolvingSubjectsDialog.Dispose();
|
|
|
|
if (pool.Connection.Session != null)
|
|
pool.Connection.Session.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(Session_PropertyChanged);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles whether the RBAC controls are visible on this control
|
|
/// </summary>
|
|
/// <param name="p"></param>
|
|
private void SetRbacVisible(bool p)
|
|
{
|
|
ButtonChangeRoles.Visible = p;
|
|
toolStripMenuItemChangeRoles.Visible = p;
|
|
GridViewSubjectList.Columns["ColumnRoles"].Visible = p;
|
|
}
|
|
|
|
/// <summary>
|
|
/// We keep track of the actions in currently running so we can disable the tab if we are in the middle of configuring AD.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
void History_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
//Program.AssertOnEventThread();
|
|
Program.BeginInvoke(Program.MainWindow, () =>
|
|
{
|
|
if (e.Action == CollectionChangeAction.Add &&
|
|
(e.Element is EnableAdAction ||
|
|
e.Element is DisableAdAction))
|
|
{
|
|
AsyncAction action = (AsyncAction) e.Element;
|
|
action.Completed += action_Completed;
|
|
|
|
if (_xenObject != null &&
|
|
_xenObject.Connection == action.Connection)
|
|
checkAdType();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void action_Completed(ActionBase sender)
|
|
{
|
|
AsyncAction action = (AsyncAction)sender;
|
|
action.Completed -= action_Completed;
|
|
|
|
if (_xenObject != null && _xenObject.Connection == action.Connection)
|
|
Program.Invoke(Program.MainWindow, checkAdType);
|
|
}
|
|
|
|
void Session_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
Program.BeginInvoke(this,RepopulateListBox);
|
|
}
|
|
|
|
/// <summary>
|
|
/// We need to update the configuration if the authentication method changes, and also various labels display the name of the
|
|
/// master and should also be updated if that changes.
|
|
/// </summary>
|
|
/// <param name="sender1"></param>
|
|
/// <param name="e"></param>
|
|
void master_PropertyChanged(object sender1, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "external_auth_type" || e.PropertyName == "name_label")
|
|
{
|
|
Program.Invoke(Program.MainWindow, checkAdType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// various labels display the name of the pool and should also be updated if that changes. Additionally if the pool master changes
|
|
/// we need to update our event handles. There is a sanity check in the checkAdType() method in case this event is stuck in a queue.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
void pool_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "name_label")
|
|
{
|
|
Program.Invoke(Program.MainWindow, checkAdType);
|
|
}
|
|
else if (e.PropertyName == "master")
|
|
{
|
|
if (master != null)
|
|
master.PropertyChanged -= new PropertyChangedEventHandler(master_PropertyChanged);
|
|
|
|
Program.Invoke(Program.MainWindow, RefreshMaster);
|
|
}
|
|
}
|
|
|
|
private void SetSubjectListEnable(bool enable)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
GridViewSubjectList.Enabled = enable;
|
|
if (enable)
|
|
{
|
|
// Grid views do a bad job of looking disabled - give it a hand
|
|
//GridViewSubjectList.ColumnHeadersVisible = true;
|
|
LabelGridViewDisabled.Visible = false;
|
|
RepopulateListBox();
|
|
// Expand admin row
|
|
foreach (AdSubjectRow r in GridViewSubjectList.Rows)
|
|
{
|
|
if (r.IsLocalRootRow)
|
|
{
|
|
r.toggleExpandedState();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Grid views do a bad job of looking disabled - give it a hand
|
|
//GridViewSubjectList.ColumnHeadersVisible = false;
|
|
LabelGridViewDisabled.Visible = true;
|
|
GridViewSubjectList.Rows.Clear();
|
|
}
|
|
}
|
|
|
|
private void checkAdType()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (master == null)
|
|
{
|
|
log.WarnFormat("Could not resolve pool master for connection '{0}' in constructor; disabling.",
|
|
Helpers.GetName(pool.Connection).Ellipsise(100));
|
|
OnMasterUnavailable();
|
|
return;
|
|
}
|
|
// Sanity check in case the master change events are queued up
|
|
if (pool.master.opaque_ref != master.opaque_ref)
|
|
{
|
|
master.PropertyChanged -= new PropertyChangedEventHandler(master_PropertyChanged);
|
|
RefreshMaster();
|
|
}
|
|
AsyncAction a = HelpersGUI.FindActiveAdAction(master.Connection);
|
|
if (a != null)
|
|
{
|
|
OnAdConfiguring();
|
|
}
|
|
else if (master.external_auth_type == Auth.AUTH_TYPE_NONE)
|
|
{
|
|
// AD is not yet configured.
|
|
OnAdDisabled();
|
|
}
|
|
else
|
|
{
|
|
if (master.external_auth_type != Auth.AUTH_TYPE_AD)
|
|
{
|
|
log.WarnFormat("Unrecognised value '{0}' for external_auth_type on pool master '{1}' for pool '{2}' in constructor; assuming AD enabled on pool.",
|
|
master.external_auth_type, Helpers.GetName(master).Ellipsise(100), Helpers.GetName(pool).Ellipsise(100));
|
|
}
|
|
// AD is already configured.
|
|
OnAdEnabled();
|
|
}
|
|
}
|
|
|
|
internal void SubjectCollectionChanged(object sender, EventArgs e)
|
|
{
|
|
Program.BeginInvoke(this, () =>
|
|
{
|
|
|
|
if (!GridViewSubjectList.Enabled)
|
|
return;
|
|
|
|
foreach (AdSubjectRow row in GridViewSubjectList.Rows)
|
|
{
|
|
if (row.IsLocalRootRow)
|
|
continue;
|
|
row.subject.PropertyChanged -=
|
|
new PropertyChangedEventHandler(subject_PropertyChanged);
|
|
}
|
|
RepopulateListBox();
|
|
});
|
|
}
|
|
|
|
private string Domain
|
|
{
|
|
get
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (pool == null)
|
|
return "";
|
|
// Resolve master
|
|
Host master = Helpers.GetMaster(pool.Connection);
|
|
if (master == null)
|
|
{
|
|
log.WarnFormat("Could not resolve pool master for connection '{0}'; disabling.",
|
|
Helpers.GetName(pool.Connection).Ellipsise(50));
|
|
return Messages.UNKNOWN;
|
|
}
|
|
|
|
// Determine AD domain from master
|
|
string domain = master.external_auth_service_name;
|
|
return domain.Ellipsise(30);
|
|
}
|
|
}
|
|
|
|
private void OnAdEnabled()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
flowLayoutPanel1.Enabled = true;
|
|
SetSubjectListEnable(true);
|
|
buttonJoinLeave.Text = Messages.AD_LEAVE_DOMAIN;
|
|
buttonJoinLeave.Enabled = true;
|
|
labelBlurb.Text = string.Format(pool.name_label.Length > 0 ? Messages.AD_CONFIGURED_BLURB : Messages.AD_CONFIGURED_BLURB_HOST, Helpers.GetName(pool).Ellipsise(70), Domain);
|
|
pool.Connection.Cache.RegisterBatchCollectionChanged<Subject>(SubjectCollectionChanged);
|
|
}
|
|
|
|
private void OnAdDisabled()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
flowLayoutPanel1.Enabled = false;
|
|
SetSubjectListEnable(false);
|
|
buttonJoinLeave.Text = Messages.AD_JOIN_DOMAIN;
|
|
buttonJoinLeave.Enabled = true;
|
|
labelBlurb.Text = string.Format(pool.name_label.Length > 0 ? Messages.AD_NOT_CONFIGURED_BLURB : Messages.AD_NOT_CONFIGURED_BLURB_HOST,
|
|
Helpers.GetName(pool).Ellipsise(70));
|
|
pool.Connection.Cache.DeregisterBatchCollectionChanged<Subject>(SubjectCollectionChanged);
|
|
}
|
|
|
|
private void OnAdConfiguring()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
flowLayoutPanel1.Enabled = false;
|
|
SetSubjectListEnable(false);
|
|
buttonJoinLeave.Enabled = false;
|
|
labelBlurb.Text = string.Format(pool.name_label.Length > 0 ? Messages.AD_CONFIGURING_BLURB : Messages.AD_CONFIGURING_BLURB_HOST,
|
|
Helpers.GetName(pool).Ellipsise(70));
|
|
}
|
|
|
|
private void OnMasterUnavailable()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
flowLayoutPanel1.Enabled = false;
|
|
SetSubjectListEnable(false);
|
|
buttonJoinLeave.Enabled = false;
|
|
labelBlurb.Text = Messages.AD_MASTER_UNAVAILABLE_BLURB;
|
|
}
|
|
|
|
private void RepopulateListBox()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
Dictionary<string, bool> selectedSubjectUuids = new Dictionary<string, bool>();
|
|
Dictionary<string, bool> expandedSubjectUuids = new Dictionary<string, bool>();
|
|
bool rootExpanded = false;
|
|
string topSubject = "";
|
|
if (GridViewSubjectList.FirstDisplayedScrollingRowIndex > 0)
|
|
{
|
|
AdSubjectRow topRow = GridViewSubjectList.Rows[GridViewSubjectList.FirstDisplayedScrollingRowIndex] as AdSubjectRow;
|
|
if (topRow.subject != null)
|
|
topSubject = topRow.subject.uuid;
|
|
}
|
|
|
|
if (GridViewSubjectList.SelectedRows.Count > 0)
|
|
{
|
|
foreach (AdSubjectRow r in GridViewSubjectList.SelectedRows)
|
|
{
|
|
if (r.subject != null)
|
|
selectedSubjectUuids.Add(r.subject.uuid, true);
|
|
}
|
|
}
|
|
foreach (AdSubjectRow row in GridViewSubjectList.Rows)
|
|
{
|
|
if (row.Expanded)
|
|
if (row.subject == null)
|
|
rootExpanded = true;
|
|
else
|
|
expandedSubjectUuids.Add(row.subject.uuid, true);
|
|
}
|
|
|
|
try
|
|
{
|
|
GridViewSubjectList.SuspendLayout();
|
|
// Populate list of authenticated users
|
|
GridViewSubjectList.Rows.Clear();
|
|
|
|
// Add local root account, a null value for the subject shows this
|
|
AdSubjectRow adminRow = new AdSubjectRow(null);
|
|
GridViewSubjectList.Rows.Add(adminRow);
|
|
|
|
List<DataGridViewRow> rows = new List<DataGridViewRow>();
|
|
Session session = pool.Connection.Session;
|
|
// Add all other Subjects in the server list
|
|
foreach (Subject subject in pool.Connection.Cache.Subjects)
|
|
{
|
|
subject.PropertyChanged += new PropertyChangedEventHandler(subject_PropertyChanged);
|
|
AdSubjectRow r = new AdSubjectRow(subject);
|
|
// we show them as unknown logged in status until the background thread updates them
|
|
r.showStatusLost();
|
|
rows.Add(r);
|
|
}
|
|
GridViewSubjectList.Rows.AddRange(rows.ToArray());
|
|
GridViewSubjectList.Sort(GridViewSubjectList.SortedColumn ?? GridViewSubjectList.Columns[2],
|
|
GridViewSubjectList.SortOrder == SortOrder.Ascending ? ListSortDirection.Ascending : ListSortDirection.Descending);
|
|
|
|
// restore old selection, old expansion state and top row
|
|
foreach (AdSubjectRow r in GridViewSubjectList.Rows)
|
|
{
|
|
r.Selected = r.subject != null && selectedSubjectUuids.ContainsKey(r.subject.uuid);
|
|
r.Expanded = r.subject != null && expandedSubjectUuids.ContainsKey(r.subject.uuid);
|
|
if (r.subject == null && rootExpanded)
|
|
r.Expanded = true;
|
|
if (r.subject != null && topSubject == r.subject.uuid)
|
|
GridViewSubjectList.FirstDisplayedScrollingRowIndex = r.Index;
|
|
}
|
|
if (GridViewSubjectList.SelectedRows.Count == 0)
|
|
GridViewSubjectList.Rows[0].Selected = true;
|
|
|
|
HelpersGUI.ResizeLastGridViewColumn(ColumnSubject);
|
|
HelpersGUI.ResizeLastGridViewColumn(ColumnRoles);
|
|
HelpersGUI.ResizeLastGridViewColumn(ColumnStatus);
|
|
}
|
|
finally
|
|
{
|
|
GridViewSubjectList.ResumeLayout();
|
|
}
|
|
}
|
|
|
|
void subject_PropertyChanged(object sender1, PropertyChangedEventArgs e)
|
|
{
|
|
Program.BeginInvoke(this, () =>
|
|
{
|
|
|
|
if (!GridViewSubjectList.Enabled || e.PropertyName != "roles")
|
|
return;
|
|
|
|
foreach (AdSubjectRow row in GridViewSubjectList.Rows)
|
|
{
|
|
if (row.IsLocalRootRow)
|
|
continue;
|
|
row.subject.PropertyChanged -=
|
|
new PropertyChangedEventHandler(subject_PropertyChanged);
|
|
}
|
|
RepopulateListBox();
|
|
});
|
|
}
|
|
|
|
|
|
private object statusUpdaterLock = new object();
|
|
/// <summary>
|
|
/// Background thread called periodically to update subjects logged in status.
|
|
/// </summary>
|
|
private void updateLoggedInStatus()
|
|
{
|
|
// This could get a bit spammy with a repeated exception, consider a back off or summary approach if it becomes an issue
|
|
Program.AssertOffEventThread();
|
|
Pool p;
|
|
while (!Disposing && !IsDisposed)
|
|
{
|
|
try
|
|
{
|
|
|
|
bool showing = false;
|
|
Program.Invoke(Program.MainWindow, delegate
|
|
{
|
|
showing = Program.MainWindow.TheTabControl.SelectedTab == Program.MainWindow.TabPageAD;
|
|
|
|
});
|
|
if (showing)
|
|
{
|
|
// In case the value gets altered underneath us
|
|
p = pool;
|
|
String[] loggedInSids = p.Connection.Session.get_all_subject_identifiers();
|
|
// Want fast access time for when we take the lock and switch off the background thread
|
|
Dictionary<string, bool> loggedSids = new Dictionary<string, bool>();
|
|
foreach (string s in loggedInSids)
|
|
loggedSids.Add(s, true);
|
|
Program.Invoke(Program.MainWindow, delegate
|
|
{
|
|
foreach (AdSubjectRow r in GridViewSubjectList.Rows)
|
|
{
|
|
// local root row
|
|
if (r.IsLocalRootRow)
|
|
continue;
|
|
|
|
r.LoggedIn = loggedSids.ContainsKey(r.subject.subject_identifier);
|
|
}
|
|
// This will update the enablement of the buttons - more specifically the log out one
|
|
GridViewSubjectList_SelectionChanged(this, null);
|
|
});
|
|
}
|
|
lock (statusUpdaterLock)
|
|
{
|
|
Monitor.Wait(statusUpdaterLock, 5000);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
showLoggedInStatusError();
|
|
log.Error(e);
|
|
System.Threading.Thread.Sleep(5000);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void showLoggedInStatusError()
|
|
{
|
|
Program.Invoke(Program.MainWindow, delegate
|
|
{
|
|
foreach (AdSubjectRow r in GridViewSubjectList.Rows)
|
|
{
|
|
if (r.IsLocalRootRow)
|
|
continue;
|
|
|
|
r.showStatusLost();
|
|
}
|
|
});
|
|
}
|
|
|
|
#region Custom AD Row Class
|
|
|
|
/// <summary>
|
|
/// Used in the DataGridView on the ConfigureAdDialog. Stores information about the subject and the different text to show if the
|
|
/// row is expanded or not.
|
|
/// </summary>
|
|
internal class AdSubjectRow : DataGridViewRow
|
|
{
|
|
internal Subject subject;
|
|
private bool expanded = false;
|
|
private bool loggedIn = false;
|
|
private bool statusLost = false;
|
|
|
|
public bool LoggedIn
|
|
{
|
|
get
|
|
{
|
|
return loggedIn;
|
|
}
|
|
set
|
|
{
|
|
loggedIn = value;
|
|
statusLost = false;
|
|
Cells[4].Value = IsLocalRootRow || subject.IsGroup ? "-"
|
|
: loggedIn ? Messages.YES : Messages.NO;
|
|
}
|
|
}
|
|
|
|
internal bool IsLocalRootRow
|
|
{
|
|
get { return subject == null; }
|
|
}
|
|
|
|
// Each entry can show a summary of roles or a full list depending on whether it is expanded or contracted
|
|
private string expandedRoles, contractedRoles;
|
|
// Expanded subject info is the key pair mapping detailed in the subjects other config along with their display name
|
|
// The contracted version is just their display name
|
|
private List<KeyValuePair<string, string>> expandedSubjectInfo, contractedSubjectInfo;
|
|
|
|
/// <summary>
|
|
/// A DataGridViewRow that corresponds to a subject and shows their relevant information in expanded and collapsed states
|
|
/// </summary>
|
|
/// <param name="subject">If set to null this is assumed to be a root account that does not have a subject</param>
|
|
internal AdSubjectRow(Subject subject)
|
|
: base()
|
|
{
|
|
this.subject = subject;
|
|
if (IsLocalRootRow)
|
|
{
|
|
contractedRoles = expandedRoles = "";
|
|
expandedSubjectInfo = new List<KeyValuePair<String, String>>();
|
|
contractedSubjectInfo = new List<KeyValuePair<String, String>>();
|
|
expandedSubjectInfo.Add(new KeyValuePair<string, string>(Messages.AD_LOCAL_ROOT_ACCOUNT, ""));
|
|
expandedSubjectInfo.Add(new KeyValuePair<string, string>("", ""));
|
|
expandedSubjectInfo.Add(new KeyValuePair<string, string>(Messages.AD_ALWAYS_GRANTED_ACCESS, ""));
|
|
contractedSubjectInfo.Add(new KeyValuePair<string, string>(Messages.AD_LOCAL_ROOT_ACCOUNT, ""));
|
|
}
|
|
else
|
|
{
|
|
if (!Helpers.MidnightRideOrGreater(subject.Connection))
|
|
{
|
|
expandedRoles = contractedRoles = "";
|
|
}
|
|
else
|
|
{
|
|
//Generate the role list
|
|
string s = "";
|
|
List<Role> roles = subject.Connection.ResolveAll<Role>(subject.roles);
|
|
roles.Sort();
|
|
roles.Reverse();
|
|
foreach (Role r in roles)
|
|
{
|
|
s = String.Format("{0}\n{1}", s, r.FriendlyName);
|
|
}
|
|
expandedRoles = s;
|
|
contractedRoles = roles.Count > 0 ?
|
|
roles.Count > 1 ? roles[0].FriendlyName.AddEllipsis() : roles[0].FriendlyName
|
|
: "";
|
|
|
|
}
|
|
contractedSubjectInfo = new List<KeyValuePair<String, String>>();
|
|
contractedSubjectInfo.Add(new KeyValuePair<string, string>(subject.DisplayName ?? subject.SubjectName ?? "", ""));
|
|
expandedSubjectInfo = Subject.ExtractKvpInfo(subject);
|
|
}
|
|
Cells.Add(new DataGridViewImageCell()); // Expander image cell
|
|
Cells.Add(new DataGridViewImageCell()); // Group/user image cell
|
|
Cells.Add(new KeyValuePairCell()); // Subject info cell
|
|
Cells.Add(new DataGridViewTextBoxCell()); // Roles cell
|
|
Cells.Add(new DataGridViewTextBoxCell()); // logged in cell
|
|
|
|
refreshCellContent();
|
|
}
|
|
|
|
public void refreshCellContent()
|
|
{
|
|
Cells[0].Value = expanded ? Resources.expanded_triangle : Resources.contracted_triangle;
|
|
Cells[1].Value = IsLocalRootRow || !subject.IsGroup ?
|
|
Resources._000_User_h32bit_16 : Resources._000_UserAndGroup_h32bit_32;
|
|
Cells[2].Value = expanded ? expandedSubjectInfo : contractedSubjectInfo;
|
|
Cells[3].Value = expanded ? expandedRoles : contractedRoles;
|
|
Cells[4].Value = IsLocalRootRow || subject.IsGroup || statusLost ? "-"
|
|
: loggedIn ? Messages.YES : Messages.NO;
|
|
|
|
}
|
|
|
|
public void toggleExpandedState()
|
|
{
|
|
expanded = !expanded;
|
|
refreshCellContent();
|
|
}
|
|
|
|
public bool Expanded
|
|
{
|
|
get
|
|
{
|
|
return expanded;
|
|
}
|
|
set
|
|
{
|
|
expanded = value;
|
|
refreshCellContent();
|
|
}
|
|
}
|
|
|
|
public void showStatusLost()
|
|
{
|
|
statusLost = true;
|
|
Cells[4].Value = "-";
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private void buttonJoinLeave_Click(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (buttonJoinLeave.Text == Messages.AD_JOIN_DOMAIN)
|
|
{
|
|
// We're enabling AD
|
|
// Obtain domain, username and password
|
|
|
|
joinPrompt.ShowDialog(this);
|
|
// Blocking for a long time, check we haven't had the dialog disposed under us
|
|
if (Disposing || IsDisposed)
|
|
return;
|
|
|
|
if (joinPrompt.DialogResult == DialogResult.Cancel)
|
|
{
|
|
joinPrompt.ClearPassword();
|
|
return;
|
|
}
|
|
|
|
EnableAdAction action = new EnableAdAction(pool, joinPrompt.Domain, joinPrompt.Username, joinPrompt.Password);
|
|
if (pool.name_label.Length > 0)
|
|
action.Pool = pool;
|
|
else
|
|
action.Host = Helpers.GetMaster(pool.Connection);
|
|
action.RunAsync();
|
|
joinPrompt.ClearPassword();
|
|
}
|
|
else
|
|
{
|
|
// We're disabling AD
|
|
|
|
// Warn if the user will boot himself out by disabling AD
|
|
Session session = pool.Connection.Session;
|
|
if (session == null)
|
|
return;
|
|
|
|
if (session.IsLocalSuperuser)
|
|
{
|
|
// User is authenticated using local root account. Confirm anyway.
|
|
string msg = string.Format(Messages.AD_LEAVE_CONFIRM,
|
|
Helpers.GetName(pool).Ellipsise(50).EscapeAmpersands(), Domain);
|
|
|
|
DialogResult r = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
null,
|
|
msg,
|
|
Messages.AD_FEATURE_NAME),
|
|
ThreeButtonDialog.ButtonYes,
|
|
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(this);
|
|
|
|
//CA-64818: DialogResult can be No if the No button has been hit
|
|
//or Cancel if the dialog has been closed from the control box
|
|
if (r != DialogResult.Yes)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Warn user will be booted out.
|
|
string msg = string.Format(pool.name_label.Length > 0 ? Messages.AD_LEAVE_WARNING : Messages.AD_LEAVE_WARNING_HOST,
|
|
Helpers.GetName(pool).Ellipsise(50), Domain);
|
|
|
|
DialogResult r = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(SystemIcons.Warning, msg, Messages.ACTIVE_DIRECTORY_TAB_TITLE),
|
|
ThreeButtonDialog.ButtonYes,
|
|
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(this);
|
|
|
|
//CA-64818: DialogResult can be No if the No button has been hit
|
|
//or Cancel if the dialog has been closed from the control box
|
|
if (r != DialogResult.Yes)
|
|
return;
|
|
}
|
|
|
|
|
|
Host master = Helpers.GetMaster(pool.Connection);
|
|
if (master == null)
|
|
{
|
|
// Really shouldn't happen unless we have been very slow with the cache
|
|
log.Error("Could not retrieve master when trying to look up domain..");
|
|
throw new Exception(Messages.CONNECTION_IO_EXCEPTION);
|
|
}
|
|
AdPasswordPrompt passPrompt = new AdPasswordPrompt(false, master.external_auth_service_name);
|
|
DialogResult result = passPrompt.ShowDialog(Program.MainWindow);
|
|
if (result == DialogResult.Cancel)
|
|
return;
|
|
|
|
Dictionary<string, string> creds = new Dictionary<string, string>();
|
|
if (result != DialogResult.Ignore)
|
|
{
|
|
creds.Add(DisableAdAction.KEY_USER, passPrompt.Username);
|
|
creds.Add(DisableAdAction.KEY_PASSWORD, passPrompt.Password);
|
|
}
|
|
DisableAdAction action = new DisableAdAction(pool, creds);
|
|
if (pool.name_label.Length > 0)
|
|
action.Pool = pool;
|
|
else
|
|
action.Host = Helpers.GetMaster(pool.Connection);
|
|
action.RunAsync();
|
|
}
|
|
}
|
|
|
|
private void buttonResolve_Click(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (!buttonAdd.Enabled)
|
|
return;
|
|
resolvingSubjectsDialog = new ResolvingSubjectsDialog(pool);
|
|
resolvingSubjectsDialog.ShowDialog();
|
|
}
|
|
|
|
|
|
|
|
private void GridViewSubjectList_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (e.Button != MouseButtons.Left)
|
|
return;
|
|
|
|
if (e.RowIndex < 0)
|
|
// The click is on a column header
|
|
return;
|
|
AdSubjectRow row = GridViewSubjectList.Rows[e.RowIndex] as AdSubjectRow;
|
|
row.toggleExpandedState();
|
|
}
|
|
|
|
private void ButtonRemove_Click(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
// Double check, this method is called from a context menu as well and the state could have changed under it
|
|
if (!ButtonRemove.Enabled)
|
|
return;
|
|
|
|
List<Subject> subjectsToRemove = new List<Subject>();
|
|
foreach (AdSubjectRow r in GridViewSubjectList.SelectedRows)
|
|
subjectsToRemove.Add(r.subject);
|
|
|
|
var removeMessage = subjectsToRemove.Count == 1
|
|
? string.Format(Messages.QUESTION_REMOVE_AD_USER_ONE, subjectsToRemove[0].DisplayName ?? subjectsToRemove[0].SubjectName)
|
|
: string.Format(Messages.QUESTION_REMOVE_AD_USER_MANY, subjectsToRemove.Count);
|
|
|
|
DialogResult questionDialog = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
null,
|
|
removeMessage,
|
|
Messages.AD_FEATURE_NAME),
|
|
ThreeButtonDialog.ButtonYes,
|
|
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(this);
|
|
|
|
//CA-64818: DialogResult can be No if the No button has been hit
|
|
//or Cancel if the dialog has been closed from the control box
|
|
if (questionDialog != DialogResult.Yes)
|
|
return;
|
|
|
|
// Warn if user is revoking his currently-in-use credentials
|
|
Session session = pool.Connection.Session;
|
|
if (session != null && session.Subject != null)
|
|
{
|
|
foreach (Subject entry in subjectsToRemove)
|
|
{
|
|
if (entry.opaque_ref == session.Subject)
|
|
{
|
|
string subjectName = entry.DisplayName ?? entry.SubjectName;
|
|
if (subjectName == null)
|
|
{
|
|
subjectName = entry.subject_identifier;
|
|
}
|
|
else
|
|
{
|
|
subjectName = subjectName.Ellipsise(256);
|
|
}
|
|
string msg = string.Format(entry.IsGroup ? Messages.AD_CONFIRM_SUICIDE_GROUP : Messages.AD_CONFIRM_SUICIDE,
|
|
subjectName, Helpers.GetName(pool).Ellipsise(50));
|
|
|
|
DialogResult r = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
SystemIcons.Warning,
|
|
msg,
|
|
Messages.AD_FEATURE_NAME),
|
|
ThreeButtonDialog.ButtonYes,
|
|
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(this);
|
|
|
|
//CA-64818: DialogResult can be No if the No button has been hit
|
|
//or Cancel if the dialog has been closed from the control box
|
|
if (r != DialogResult.Yes)
|
|
return;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
removeUserDialog = new ActionProgressDialog(
|
|
new AddRemoveSubjectsAction(pool, new List<string>(), subjectsToRemove), ProgressBarStyle.Continuous);
|
|
removeUserDialog.ShowDialog();
|
|
}
|
|
|
|
private void GridViewSubjectList_SelectionChanged(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (GridViewSubjectList.SelectedRows.Count < 1)
|
|
return;
|
|
|
|
bool adminSelected = GridViewSubjectList.Rows[0].Selected;
|
|
bool multipleSelected = GridViewSubjectList.SelectedRows.Count > 1;
|
|
|
|
tTipChangeRole.SuppressTooltip = ButtonChangeRoles.Enabled = !adminSelected;
|
|
tTipChangeRole.SetToolTip(Messages.AD_CANNOT_MODIFY_ROOT);
|
|
|
|
tTipLogoutButton.SuppressTooltip = !adminSelected;
|
|
ButtonLogout.Enabled = !adminSelected && AllSelectedRowsLoggedIn();
|
|
|
|
tTipRemoveButton.SuppressTooltip = ButtonRemove.Enabled = !adminSelected;
|
|
|
|
toolStripMenuItemChangeRoles.Enabled = ButtonChangeRoles.Enabled;
|
|
toolStripMenuItemLogout.Enabled = ButtonLogout.Enabled;
|
|
toolStripMenuItemRemove.Enabled = ButtonRemove.Enabled;
|
|
}
|
|
|
|
private bool AllSelectedRowsLoggedIn()
|
|
{
|
|
foreach (AdSubjectRow r in GridViewSubjectList.SelectedRows)
|
|
{
|
|
if (!r.LoggedIn)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
private void GridViewSubjectList_SortCompare(object sender, DataGridViewSortCompareEventArgs e)
|
|
{
|
|
// First of all, the admin row is always displayed at the top.
|
|
AdSubjectRow row1 = (AdSubjectRow)GridViewSubjectList.Rows[e.RowIndex1];
|
|
AdSubjectRow row2 = (AdSubjectRow)GridViewSubjectList.Rows[e.RowIndex2];
|
|
if (row1.IsLocalRootRow)
|
|
{
|
|
e.SortResult = GridViewSubjectList.SortOrder == SortOrder.Ascending ? -1 : 1;
|
|
}
|
|
else if (row2.IsLocalRootRow)
|
|
{
|
|
e.SortResult = GridViewSubjectList.SortOrder == SortOrder.Ascending ? 1 : -1;
|
|
}
|
|
else
|
|
{
|
|
// Next we look to see which column we are sorting on
|
|
switch (GridViewSubjectList.SortedColumn.Index)
|
|
{
|
|
case 0: // shouldn't happen (expander column)
|
|
case 1: // Group/individual picture column
|
|
e.SortResult = GroupCompare(row1.subject, row2.subject);
|
|
break;
|
|
case 2: // Name and detail column
|
|
e.SortResult = NameCompare(row1.subject, row2.subject);
|
|
break;
|
|
case 3: // Role Column
|
|
e.SortResult = RoleCompare(row1.subject, row2.subject);
|
|
break;
|
|
case 4: // Logged in column
|
|
e.SortResult = LoggedInCompare(row1, row2);
|
|
break;
|
|
}
|
|
}
|
|
e.Handled = true;
|
|
}
|
|
|
|
private int RoleCompare(Subject s1, Subject s2)
|
|
{
|
|
List<Role> s1Roles = pool.Connection.ResolveAll(s1.roles);
|
|
List<Role> s2Roles = pool.Connection.ResolveAll(s2.roles);
|
|
s1Roles.Sort();
|
|
s2Roles.Sort();
|
|
// If one subject doesn't have any roles, but it below the one with roles
|
|
if (s1Roles.Count < 1)
|
|
{
|
|
if (s2Roles.Count < 1)
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
if (s2Roles.Count < 1)
|
|
return 1;
|
|
|
|
return s1Roles[0].CompareTo(s2Roles[0]);
|
|
}
|
|
|
|
private int NameCompare(Subject s1, Subject s2)
|
|
{
|
|
return StringUtility.NaturalCompare(s1.SubjectName, s2.SubjectName) * -1;
|
|
}
|
|
|
|
private int LoggedInCompare(AdSubjectRow s1, AdSubjectRow s2)
|
|
{
|
|
if (s1.LoggedIn)
|
|
{
|
|
if (s2.LoggedIn)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if (s2.LoggedIn)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private int GroupCompare(Subject s1, Subject s2)
|
|
{
|
|
if (s1.IsGroup)
|
|
{
|
|
if (s2.IsGroup)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if (s2.IsGroup)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private void ButtonChangeRoles_Click(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (Helpers.FeatureForbidden(pool.Connection, Host.RestrictRBAC))
|
|
{
|
|
// Show upsell dialog
|
|
UpsellDialog dlg = new UpsellDialog(Messages.UPSELL_BLURB_RBAC, InvisibleMessages.UPSELL_LEARNMOREURL_RBAC);
|
|
dlg.ShowDialog(this);
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
// Double check, this method is called from a context menu as well and the state could have changed under it
|
|
if (!ButtonChangeRoles.Enabled)
|
|
return;
|
|
|
|
List<Subject> selectedSubjects = new List<Subject>();
|
|
foreach (DataGridViewRow r in GridViewSubjectList.SelectedRows)
|
|
{
|
|
AdSubjectRow selectedRow = (AdSubjectRow)r;
|
|
// Should not be here, you can't change the root man!
|
|
if (selectedRow.IsLocalRootRow)
|
|
continue;
|
|
selectedSubjects.Add(selectedRow.subject);
|
|
}
|
|
|
|
RoleSelectionDialog dialog = new RoleSelectionDialog(selectedSubjects.ToArray(), pool);
|
|
dialog.Show(this);
|
|
}
|
|
|
|
private void ButtonLogout_Click(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
// Double check, this method is called from a context menu as well and the state could have changed under it
|
|
if (!ButtonLogout.Enabled)
|
|
return;
|
|
|
|
Session session = pool.Connection.Session;
|
|
if (session == null)
|
|
return;
|
|
|
|
// First we check through the list to check what warning message we show
|
|
List<Subject> subjectsToLogout = new List<Subject>();
|
|
foreach (AdSubjectRow r in GridViewSubjectList.SelectedRows)
|
|
{
|
|
if (r.IsLocalRootRow || !r.LoggedIn)
|
|
continue;
|
|
|
|
subjectsToLogout.Add(r.subject);
|
|
}
|
|
|
|
bool suicide = false;
|
|
// Warn if user is logging themselves out
|
|
if (session.Subject != null)//have already checked session not null
|
|
{
|
|
var warnMsg = string.Format(subjectsToLogout.Count > 1 ? Messages.AD_LOGOUT_SUICIDE_MANY : Messages.AD_LOGOUT_SUICIDE_ONE,
|
|
Helpers.GetName(pool).Ellipsise(50));
|
|
|
|
foreach (Subject entry in subjectsToLogout)
|
|
{
|
|
if (entry.opaque_ref == session.Subject)
|
|
{
|
|
DialogResult r = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
SystemIcons.Warning,
|
|
warnMsg,
|
|
Messages.AD_FEATURE_NAME),
|
|
ThreeButtonDialog.ButtonYes,
|
|
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(this);
|
|
|
|
//CA-64818: DialogResult can be No if the No button has been hit
|
|
//or Cancel if the dialog has been closed from the control box
|
|
if (r != DialogResult.Yes)
|
|
return;
|
|
|
|
suicide = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var logoutMessage = subjectsToLogout.Count == 1
|
|
? string.Format(Messages.QUESTION_LOGOUT_AD_USER_ONE, subjectsToLogout[0].DisplayName ?? subjectsToLogout[0].SubjectName)
|
|
: string.Format(Messages.QUESTION_LOGOUT_AD_USER_MANY, subjectsToLogout.Count);
|
|
|
|
if (!suicide)//CA-68645
|
|
{
|
|
DialogResult questionDialog = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
SystemIcons.Warning,
|
|
logoutMessage,
|
|
Messages.AD_FEATURE_NAME),
|
|
ThreeButtonDialog.ButtonYes,
|
|
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(this);
|
|
|
|
//CA-64818: DialogResult can be No if the No button has been hit
|
|
//or Cancel if the dialog has been closed from the control box
|
|
if (questionDialog != DialogResult.Yes)
|
|
return;
|
|
}
|
|
|
|
// Then we go through the list and disconnect each user session, doing our own last if necessary
|
|
foreach (AdSubjectRow r in GridViewSubjectList.SelectedRows)
|
|
{
|
|
// check they are not the root row and are logged in
|
|
if (r.IsLocalRootRow || !r.LoggedIn)
|
|
continue;
|
|
|
|
// we suicide last
|
|
if (session.UserSid == r.subject.subject_identifier)
|
|
{
|
|
continue;
|
|
}
|
|
DelegatedAsyncAction logoutAction = new DelegatedAsyncAction(pool.Connection, Messages.TERMINATING_SESSIONS, Messages.IN_PROGRESS, Messages.COMPLETED, delegate(Session s)
|
|
{
|
|
Session.logout_subject_identifier(s, r.subject.subject_identifier);
|
|
}, "session.logout_subject_identifier");
|
|
logoutAction.RunAsync();
|
|
}
|
|
if (suicide)
|
|
{
|
|
//bye bye
|
|
DelegatedAsyncAction logoutAction = new DelegatedAsyncAction(pool.Connection, Messages.TERMINATING_SESSIONS, Messages.IN_PROGRESS, Messages.COMPLETED, delegate(Session s)
|
|
{
|
|
Session.logout_subject_identifier(s, session.UserSid);
|
|
pool.Connection.Logout();
|
|
}, "session.logout_subject_identifier");
|
|
logoutAction.RunAsync();
|
|
}
|
|
else
|
|
{
|
|
// signal the background thread to update the logged in status
|
|
lock (statusUpdaterLock)
|
|
Monitor.Pulse(statusUpdaterLock);
|
|
}
|
|
}
|
|
|
|
private void GridViewSubjectList_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button != MouseButtons.Right)
|
|
return;
|
|
|
|
DataGridView.HitTestInfo i = GridViewSubjectList.HitTest(e.X, e.Y);
|
|
if (i.RowIndex < 0 || GridViewSubjectList.Rows[i.RowIndex].Selected)
|
|
return;
|
|
|
|
GridViewSubjectList.ClearSelection();
|
|
GridViewSubjectList.Rows[i.RowIndex].Selected = true;
|
|
}
|
|
}
|
|
}
|