xenadmin/XenAdmin/TabPages/AdPage.cs
2015-04-16 14:17:02 +01:00

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(this, 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(this, 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(this, checkAdType);
}
else if (e.PropertyName == "master")
{
if (master != null)
master.PropertyChanged -= new PropertyChangedEventHandler(master_PropertyChanged);
Program.Invoke(this, 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(this, 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(this, 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(this, 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;
}
}
}