xenadmin/XenModel/Actions/AD/AddRemoveSubjectsAction.cs
Gabor Apati-Nagy 7c0bc50b4a CA-176169: Changed copyright statements to include the comma in Citrix Systems,
Inc.

Signed-off-by: Gabor Apati-Nagy<gabor.apati-nagy@citrix.com>
2017-01-16 19:59:50 +00:00

288 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 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(Pool pool, List<string> SubjectNamesToAdd, List<Subject> SubjectsToRemove)
: base(
pool.Connection,
string.Format(Messages.AD_ADDING_REMOVING_ON, Helpers.GetName(pool).Ellipsise(50)),
Messages.AD_ADDING_REMOVING, false)
{
this.Pool = pool;
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)
Pool.Connection.Logout();
}
private void resolveSubjects()
{
Exception e = null;
string resolvedName = "";
string sid = "";
log.DebugFormat("Resolving AD entries on pool '{0}'", Helpers.GetName(Pool).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("Exception resolving AD user", 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(Pool).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 Pool.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(Pool).Ellipsise(50));
string selfSid = Pool.Connection.Session.IsLocalSuperuser || Pool.Connection.Session.Subject == null ? "" :
Pool.Connection.Resolve<Subject>(Pool.Connection.Session.Subject).subject_identifier;
foreach (Subject subject in subjectsToRemove)
{
string sid = subject.subject_identifier;
try
{
if (!Pool.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;
}
}
}
}