2017-01-16 20:59:50 +01:00
/ * Copyright ( c ) Citrix Systems , Inc .
2013-06-24 13:41:48 +02:00
* 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 ;
2018-07-27 19:18:05 +02:00
using XenAdmin.Network ;
2013-06-24 13:41:48 +02:00
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 ;
2018-07-27 19:18:05 +02:00
public AddRemoveSubjectsAction ( IXenConnection connection , List < string > subjectNamesToAdd , List < Subject > subjectsToRemove )
: base ( connection ,
string . Format ( Messages . AD_ADDING_REMOVING_ON , Helpers . GetName ( connection ) . Ellipsise ( 50 ) ) ,
2013-06-24 13:41:48 +02:00
Messages . AD_ADDING_REMOVING , false )
{
2018-07-27 19:18:05 +02:00
var pool = Helpers . GetPool ( connection ) ;
if ( pool ! = null )
Pool = pool ;
else
2021-08-31 12:31:16 +02:00
Host = Helpers . GetCoordinator ( connection ) ;
2018-07-27 19:18:05 +02:00
this . subjectNamesToAdd = subjectNamesToAdd ;
this . subjectsToRemove = subjectsToRemove ;
2013-06-24 13:41:48 +02:00
#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 )
2018-07-27 19:18:05 +02:00
Connection . Logout ( ) ;
2013-06-24 13:41:48 +02:00
}
private void resolveSubjects ( )
{
Exception e = null ;
string resolvedName = "" ;
string sid = "" ;
2019-11-08 12:42:18 +01:00
log . DebugFormat ( "Resolving AD entries on pool '{0}'." , Helpers . GetName ( Connection ) ) ;
2013-06-24 13:41:48 +02:00
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 ;
2018-07-27 19:18:05 +02:00
log . Warn ( string . Format ( "Exception resolving AD user {0}" , name ) , f ) ;
2013-06-24 13:41:48 +02:00
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 ;
2019-11-08 12:42:18 +01:00
log . DebugFormat ( "Adding {0} new subjects on pool '{1}'" , sidsToAdd . Count , Helpers . GetName ( Connection ) ) ;
2013-06-24 13:41:48 +02:00
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
2018-07-27 19:18:05 +02:00
foreach ( Subject s in Connection . Cache . Subjects )
2013-06-24 13:41:48 +02:00
{
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 ;
2019-11-08 12:42:18 +01:00
log . DebugFormat ( "Removing {0} existing subjects on pool '{1}'." , subjectsToRemove . Count , Helpers . GetName ( Connection ) ) ;
2020-01-30 01:02:24 +01:00
string selfSid = Connection . Session . IsLocalSuperuser | | Connection . Session . SessionSubject = = null
2018-07-27 19:18:05 +02:00
? ""
2020-01-30 01:02:24 +01:00
: Connection . Resolve ( Connection . Session . SessionSubject ) . subject_identifier ;
2018-07-27 19:18:05 +02:00
2013-06-24 13:41:48 +02:00
foreach ( Subject subject in subjectsToRemove )
{
string sid = subject . subject_identifier ;
try
{
2018-07-27 19:18:05 +02:00
if ( ! Connection . Session . IsLocalSuperuser & & selfSid = = sid )
2013-06-24 13:41:48 +02:00
{
2021-11-15 12:48:46 +01:00
// We will log ourselves out later.
2013-06-24 13:41:48 +02:00
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 ;
}
}
}
}