mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-20 23:46:03 +01:00
7c0bc50b4a
Inc. Signed-off-by: Gabor Apati-Nagy<gabor.apati-nagy@citrix.com>
288 lines
12 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|