2023-01-24 15:29:31 +01:00
/ * Copyright ( c ) Cloud Software Group , Inc .
2013-06-24 13:41:48 +02:00
*
* 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 ;
2023-08-02 13:04:04 +02:00
using System.Linq ;
2013-06-24 13:41:48 +02:00
using XenAdmin.Core ;
using XenAdmin.Network ;
namespace XenAPI
{
// Note that the Role object represents both the high-level roles (such as "VM Operator" etc.)
// and their subroles, i.e., the individual calls they are allowed to make (such as "vm.create").
2019-09-23 15:56:36 +02:00
public partial class Role
2013-06-24 13:41:48 +02:00
{
private static readonly log4net . ILog log = log4net . LogManager . GetLogger ( System . Reflection . MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
public const string MR_ROLE_READ_ONLY = "read-only" ;
public const string MR_ROLE_VM_OPERATOR = "vm-operator" ;
public const string MR_ROLE_VM_ADMIN = "vm-admin" ;
public const string MR_ROLE_VM_POWER_ADMIN = "vm-power-admin" ;
public const string MR_ROLE_POOL_OPERATOR = "pool-operator" ;
public const string MR_ROLE_POOL_ADMIN = "pool-admin" ;
2017-09-03 04:33:29 +02:00
public string FriendlyName ( )
2013-06-24 13:41:48 +02:00
{
2023-08-02 13:04:04 +02:00
return FriendlyNameManager . GetFriendlyName ( $"Role.{name_label.ToLowerInvariant()}.NameLabel" ) ;
2019-09-23 15:56:36 +02:00
}
public static string FriendlyName ( string role )
{
2023-08-02 13:04:04 +02:00
return FriendlyNameManager . GetFriendlyName ( $"Role.{role.ToLowerInvariant()}.NameLabel" ) ;
2013-06-24 13:41:48 +02:00
}
2017-09-03 04:33:29 +02:00
public string FriendlyDescription ( )
2013-06-24 13:41:48 +02:00
{
2023-08-02 13:04:04 +02:00
return FriendlyNameManager . GetFriendlyName ( $"Role.{name_label.ToLowerInvariant()}.Description" ) ;
2013-06-24 13:41:48 +02:00
}
2017-09-03 04:33:29 +02:00
public override string Name ( )
2013-06-24 13:41:48 +02:00
{
2017-09-03 04:33:29 +02:00
return name_label ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
/// Currently all RBAC roles can be arranged in a rank so that each one has all the privileges of the next. Use this function for sorting based on this ordering.
/// </summary>
2023-08-02 13:04:04 +02:00
private int RoleRank ( )
2013-06-24 13:41:48 +02:00
{
switch ( name_label . ToLowerInvariant ( ) )
{
case MR_ROLE_READ_ONLY : return 0 ;
case MR_ROLE_VM_OPERATOR : return 1 ;
case MR_ROLE_VM_ADMIN : return 2 ;
case MR_ROLE_VM_POWER_ADMIN : return 3 ;
case MR_ROLE_POOL_OPERATOR : return 4 ;
case MR_ROLE_POOL_ADMIN : return 5 ;
}
return - 1 ;
}
/// <summary>
/// logout, login_with_password
/// </summary>
2017-09-03 04:33:29 +02:00
public static readonly RbacMethodList CommonSessionApiList = new RbacMethodList (
2013-06-24 13:41:48 +02:00
"session.logout" ,
"session.login_with_password"
) ;
/// <summary>
/// add_to_other_config, destroy
/// </summary>
2017-09-03 04:33:29 +02:00
public static readonly RbacMethodList CommonTaskApiList = new RbacMethodList (
2013-06-24 13:41:48 +02:00
new RbacMethod ( "task.add_to_other_config" , "XenCenterUUID" ) , // See AsyncAction.RelatedTask
new RbacMethod ( "task.add_to_other_config" , "applies_to" ) ,
new RbacMethod ( "task.destroy" )
) ;
/// <summary>
2023-08-02 13:04:04 +02:00
/// Takes a list of role objects and returns as a comma separated friendly string.
/// Note that it excludes xapi internal roles.
2013-06-24 13:41:48 +02:00
/// </summary>
2023-08-02 12:44:05 +02:00
public static string FriendlyCsvRoleList ( List < Role > roles )
2013-06-24 13:41:48 +02:00
{
2023-08-02 13:04:04 +02:00
return roles = = null
? string . Empty
: string . Join ( ", " , roles . Where ( r = > ! r . _is_internal ) . Select ( r = > r . FriendlyName ( ) ) ) ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
2022-01-29 03:50:54 +01:00
/// Retrieves all the server RBAC roles which are able to complete all the api methods supplied.
/// If on George or less this will return an empty list.
2013-06-24 13:41:48 +02:00
/// </summary>
2022-01-29 03:50:54 +01:00
/// <param name="apiMethodsToRoleCheck">list of RbacMethods to check</param>
/// <param name="connection">server connection to retrieve roles from</param>
public static List < Role > ValidRoleList ( RbacMethodList apiMethodsToRoleCheck , IXenConnection connection )
2013-06-24 13:41:48 +02:00
{
2022-01-29 03:50:54 +01:00
log . DebugFormat ( "Checking roles required to complete the following calls: {0}" ,
string . Join ( ", " , apiMethodsToRoleCheck . ToStringArray ( ) ) ) ;
var rolesAbleToCompleteAction = new List < Role > ( ) ;
foreach ( RbacMethod method in apiMethodsToRoleCheck )
2013-06-24 13:41:48 +02:00
{
2022-01-29 03:50:54 +01:00
List < Role > rolesAbleToCompleteApiCall = ValidRoleList ( method , connection ) ;
2013-06-24 13:41:48 +02:00
if ( rolesAbleToCompleteAction . Count = = 0 )
{
rolesAbleToCompleteAction . AddRange ( rolesAbleToCompleteApiCall ) ;
continue ;
}
2022-01-29 03:50:54 +01:00
//the permissions to run a list of API calls should be those of the
//most restricted call in the list; if more permissions have been added
//by the previous calls in the list, these have to be removed
if ( rolesAbleToCompleteApiCall . Count > 0 )
rolesAbleToCompleteAction . RemoveAll ( r = > ! rolesAbleToCompleteApiCall . Contains ( r ) ) ;
2013-06-24 13:41:48 +02:00
}
2022-01-29 03:50:54 +01:00
2013-06-24 13:41:48 +02:00
return rolesAbleToCompleteAction ;
}
/// <summary>
2022-01-29 03:50:54 +01:00
/// Retrieves all the server RBAC roles which are able to complete the api method supplied.
/// If on George or less this will return an empty list.
2013-06-24 13:41:48 +02:00
/// </summary>
2022-01-29 03:50:54 +01:00
/// <param name="apiMethodToRoleCheck">RbacMethod to check</param>
/// <param name="connection">server connection to retrieve roles from</param>
private static List < Role > ValidRoleList ( RbacMethod apiMethodToRoleCheck , IXenConnection connection )
2013-06-24 13:41:48 +02:00
{
List < Role > rolesAbleToCompleteApiCall = new List < Role > ( ) ;
2022-01-29 03:50:54 +01:00
foreach ( Role role in connection . Cache . Roles )
2013-06-24 13:41:48 +02:00
{
2022-01-29 03:50:54 +01:00
List < Role > subroles = connection . ResolveAll ( role . subroles ) ;
if ( subroles . Find ( r = > r . CanPerform ( apiMethodToRoleCheck ) ) ! = null )
2013-06-24 13:41:48 +02:00
{
rolesAbleToCompleteApiCall . Add ( role ) ;
}
}
2022-01-29 03:50:54 +01:00
// On connections with roles (i.e. non-simulator connections), each API call
// should be available to at least Pool Admins (because the latter can do
// everything). No roles is usually caused by a typo in the apiMethodToRoleCheck,
// or by running a new action against an old server without checking.
if ( ! connection . HostnameWithPort . EndsWith ( ".xml" , StringComparison . InvariantCultureIgnoreCase ) )
2013-06-24 13:41:48 +02:00
{
2022-01-29 03:50:54 +01:00
var msg = $"No roles able to perform API call {apiMethodToRoleCheck}" ;
if ( rolesAbleToCompleteApiCall . Count < 1 )
log . Debug ( msg ) ;
System . Diagnostics . Debug . Assert ( rolesAbleToCompleteApiCall . Count > 0 , msg ) ;
2013-06-24 13:41:48 +02:00
}
2022-01-29 03:50:54 +01:00
2013-06-24 13:41:48 +02:00
return rolesAbleToCompleteApiCall ;
}
/// <summary>
2022-01-29 03:50:54 +01:00
/// Retrieves all the server RBAC roles which are able to complete the api method supplied.
/// If on George or less this will return an empty list.
2013-06-24 13:41:48 +02:00
/// </summary>
2022-01-29 03:50:54 +01:00
/// <param name="apiMethodToRoleCheck">object.method</param>
/// <param name="connection">server connection to retrieve roles from</param>
public static List < Role > ValidRoleList ( string apiMethodToRoleCheck , IXenConnection connection )
2013-06-24 13:41:48 +02:00
{
2022-01-29 03:50:54 +01:00
return ValidRoleList ( new RbacMethod ( apiMethodToRoleCheck ) , connection ) ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
/// Can the main session on this connection already perform all the API methods? If on George or less this will return false.
/// Also return the list of valid roles.
/// </summary>
/// <param name="apiMethodsToRoleCheck">The methods to check</param>
/// <param name="connection">The connection on which to perform the methods</param>
/// <param name="validRoleList">The list of roles which can perform all the methods</param>
2022-01-29 03:50:54 +01:00
public static bool CanPerform ( RbacMethodList apiMethodsToRoleCheck , IXenConnection connection , out List < Role > validRoleList )
2013-06-24 13:41:48 +02:00
{
2014-08-29 10:50:47 +02:00
if ( ! connection . IsConnected )
{
validRoleList = new List < Role > ( ) ;
return false ;
}
2022-01-29 03:50:54 +01:00
validRoleList = ValidRoleList ( apiMethodsToRoleCheck , connection ) ;
2013-06-24 13:41:48 +02:00
2015-10-26 17:01:55 +01:00
if ( connection . Session ! = null & & connection . Session . IsLocalSuperuser )
return true ;
foreach ( Role role in validRoleList )
2013-06-24 13:41:48 +02:00
{
2015-10-26 17:01:55 +01:00
if ( connection . Session ! = null & & connection . Session . Roles ! = null & & connection . Session . Roles . Contains ( role ) )
2013-06-24 13:41:48 +02:00
return true ;
}
return false ;
}
/// <summary>
/// Can this subrole perform this API call?
/// </summary>
/// <param name="rbacMethod">The API call which we want to perform</param>
private bool CanPerform ( RbacMethod rbacMethod )
{
// Does the method name match?
if ( name_label = = rbacMethod . Method )
return true ;
2022-01-29 03:50:54 +01:00
2013-06-24 13:41:48 +02:00
// Is the call a hash table modification, and if so, does the
// more specific name match?
if ( ! String . IsNullOrEmpty ( rbacMethod . Key ) )
{
string whole = rbacMethod . ToString ( ) ;
if ( name_label . EndsWith ( "*" ) ) // e.g. vm.add_to_other_config/key:Foo*
{
string stripped_name = name_label . TrimEnd ( '*' ) ;
if ( whole . StartsWith ( stripped_name ) )
return true ;
}
else // e.g. vm.add_to_other_config/key:Foo
{
if ( name_label = = whole )
return true ;
}
}
return false ;
}
#region XenObjectComparable < Role > Members
public override int CompareTo ( Role other )
{
int rank = RoleRank ( ) ;
int otherRank = other . RoleRank ( ) ;
return rank > otherRank ? 1 : rank < otherRank ? - 1 : 0 ;
}
public override bool Equals ( object obj )
{
2022-01-29 03:50:54 +01:00
return obj is Role r ? r . opaque_ref = = opaque_ref : base . Equals ( obj ) ;
2013-06-24 13:41:48 +02:00
}
public override int GetHashCode ( )
{
return opaque_ref . GetHashCode ( ) ;
}
#endregion
}
}