xenadmin/XenModel/Actions/AD/AddRemoveSubjectsAction.cs
Konstantina Chremmou 02eaac832a CA-294365: Replaced all references to pool or master except the necessary
with references to the connection; pool or master can be resolved from the
latter. This among others prevents the AD page from crashing when the master
and the pool change after master's entering maintenance mode.

Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2018-08-01 08:27:04 +01:00

293 lines
12 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 XenAdmin.Core;
using XenAdmin.Network;
using XenAPI;
namespace XenAdmin.Actions
{
public class AddRemoveSubjectsAction : AsyncAction
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private List<string> subjectNamesToAdd;
private readonly List<Subject> subjectsToRemove;
/// <summary>
/// Progress through this action
/// </summary>
private int stepsComplete = 0;
private int steps;
/// <summary>
/// If we remove a subject from the server we are currently using we need to log it out by hand. This value indicates whether we need to perform this aciton or not.
/// </summary>
private bool logoutSession = false;
/// <summary>
/// Successfully resolved ids we will create subjects out of
/// </summary>
private List<string> sidsToAdd;
public delegate void NameResolvedEventHandler(object sender, string enteredName, string resolvedName, string sid, Exception exception);
/// <summary>
/// Fired for each event after resolution is attempted. If succeeded exception is null, and we pass the full resolved name and sid.
/// </summary>
public event NameResolvedEventHandler NameResolveComplete;
public delegate void AllNamesResolvedEventHandler();
/// <summary>
/// Fired when all resolutions have finished.
/// </summary>
public event AllNamesResolvedEventHandler AllResolveComplete;
public delegate void SubjectAddedEventHandler(object sender, Subject subject, Exception exception);
/// <summary>
/// Event for when a single subject add has completed. If succeeded exception is null.
/// </summary>
public event SubjectAddedEventHandler SubjectAddComplete;
public delegate void SubjectRemovedEventHandler(object sender, string sid, Exception exception);
/// <summary>
/// Event for when a single subject remove has completed. If succeeded exception is null.
/// </summary>
public event SubjectRemovedEventHandler SubjectRemoveComplete;
public AddRemoveSubjectsAction(IXenConnection connection, List<string> subjectNamesToAdd, List<Subject> subjectsToRemove)
: base(connection,
string.Format(Messages.AD_ADDING_REMOVING_ON, Helpers.GetName(connection).Ellipsise(50)),
Messages.AD_ADDING_REMOVING, false)
{
var pool = Helpers.GetPool(connection);
if (pool != null)
Pool = pool;
else
Host = Helpers.GetMaster(connection);
this.subjectNamesToAdd = subjectNamesToAdd;
this.subjectsToRemove = subjectsToRemove;
#region RBAC checks
if (subjectNamesToAdd != null && subjectNamesToAdd.Count > 0)
{
ApiMethodsToRoleCheck.Add("subject.create");
}
if (subjectsToRemove != null && subjectsToRemove.Count > 0)
{
ApiMethodsToRoleCheck.Add("session.logout_subject_identifier");
ApiMethodsToRoleCheck.Add("subject.destroy");
}
#endregion
}
protected override void Run()
{
// clear out dupes
Dictionary<string, object> sNames = new Dictionary<string, object>();
List<string> uniqueList = new List<string>();
foreach (string name in subjectNamesToAdd)
{
if (sNames.ContainsKey(name))
continue;
sNames.Add(name, null);
uniqueList.Add(name);
}
subjectNamesToAdd = uniqueList;
// for each entry to add we must resolve it, then add it. Then we do the removes.
steps = subjectNamesToAdd.Count * 2 + subjectsToRemove.Count;
sidsToAdd = new List<string>();
resolveSubjects();
addResolvedSubjects();
removeSubjects();
// We have only only kept track of the latest exception no matter how many occurred - refer the user to the logs for full info.
Description = Exception == null ? Messages.COMPLETED : Messages.COMPLETED_WITH_ERRORS;
if (logoutSession)
Connection.Logout();
}
private void resolveSubjects()
{
Exception e = null;
string resolvedName = "";
string sid = "";
log.DebugFormat("Resolving AD entries on pool '{0}'", Helpers.GetName(Connection).Ellipsise(50));
foreach (string name in subjectNamesToAdd)
{
try
{
sid = Auth.get_subject_identifier(Session, name);
sidsToAdd.Add(sid);
if (!Auth.get_subject_information_from_identifier(Session, sid).TryGetValue(Subject.SUBJECT_NAME_KEY, out resolvedName))
resolvedName = Messages.UNKNOWN_AD_USER;
}
catch (Failure f)
{
if (f.ErrorDescription[0] == Failure.RBAC_PERMISSION_DENIED)
Failure.ParseRBACFailure(f,Connection,Session);
Exception = f;
log.Warn(string.Format("Exception resolving AD user {0}", name), f);
e = f;
}
finally
{
if (NameResolveComplete != null)
{
NameResolveComplete(this, name, resolvedName, sid, e);
}
e = null;
sid = resolvedName = "";
}
stepsComplete++;
PercentComplete = (100 * stepsComplete) / steps;
}
if (AllResolveComplete != null)
{
AllResolveComplete();
}
}
private void addResolvedSubjects()
{
Exception e = null;
Subject subject = null;
log.DebugFormat("Adding {0} new subjects on pool '{1}'", sidsToAdd.Count, Helpers.GetName(Connection).Ellipsise(50));
foreach (string sid in sidsToAdd)
{
try
{
// We pass this object back even if it fails so we know who we are talking about
subject = new Subject();
subject.subject_identifier = sid;
subject.other_config = Auth.get_subject_information_from_identifier(Session, sid);
// Check that this subject doesn't already exist
foreach (Subject s in Connection.Cache.Subjects)
{
if (s.subject_identifier == sid)
{
log.WarnFormat("A Subject with sid {0} already exists on the server.", sid);
string subjectName = (subject.DisplayName ?? subject.SubjectName ?? "").Ellipsise(50);
throw new Exception(String.Format(Messages.AD_USER_ALREADY_HAS_ACCESS, subjectName));
}
}
XenAPI.Subject.create(Session, subject);
}
catch (Exception ex)
{
Failure f = ex as Failure;
if (f != null && f.ErrorDescription[0] == Failure.RBAC_PERMISSION_DENIED)
Failure.ParseRBACFailure(f,Connection,Session);
Exception = ex;
log.Warn("Exception adding AD user to subject list", ex);
e = ex;
}
finally
{
if (SubjectAddComplete != null)
{
SubjectAddComplete(this, subject, e);
}
e = null;
subject = null;
}
stepsComplete++;
PercentComplete = (100 * stepsComplete) / steps;
}
}
private void removeSubjects()
{
Exception e = null;
log.DebugFormat("Removing {0} existing subjects on pool '{1}'", subjectsToRemove.Count, Helpers.GetName(Connection).Ellipsise(50));
string selfSid = Connection.Session.IsLocalSuperuser || Connection.Session.Subject == null
? ""
: Connection.Resolve(Connection.Session.Subject).subject_identifier;
foreach (Subject subject in subjectsToRemove)
{
string sid = subject.subject_identifier;
try
{
if (!Connection.Session.IsLocalSuperuser && selfSid == sid)
{
// Committing suicide. We will log ourselves out later.
logoutSession = true;
}
else
{
Session.logout_subject_identifier(Session, sid);
}
XenAPI.Subject.destroy(Session, subject.opaque_ref);
// We look at the session subject as this is the authority under which we are connected.
// (deliberate use of the original session for subject analysis... the sudo session is not the one we want to interrogate
}
catch (Exception ex)
{
Failure f = ex as Failure;
if (f != null && f.ErrorDescription[0] == Failure.RBAC_PERMISSION_DENIED)
Failure.ParseRBACFailure(f, Connection, Session);
Exception = ex;
log.Warn("Exception removing AD user to subject list", ex);
e = ex;
}
finally
{
if (SubjectRemoveComplete != null)
{
SubjectRemoveComplete(this, sid, e);
}
e = null;
}
stepsComplete++;
PercentComplete = (100 * stepsComplete) / steps;
}
}
}
}