Skip to content

Commit

Permalink
SDK fixes including CP-53003 (#6210)
Browse files Browse the repository at this point in the history
Commits best reviewed separately.

- CP-53003: Use JsonRpc v1.0 by default and switch to v2.0 once the API
version is known.
- Removal of deprecated methods and unused internal methods.
- Refactoring to fix some inefficiencies when loading and saving
certificates in PS.
  • Loading branch information
kc284 authored Jan 13, 2025
2 parents fbaad2b + fa1bf90 commit 8c9b754
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 135 deletions.
39 changes: 8 additions & 31 deletions ocaml/sdk-gen/csharp/autogen/src/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,15 @@ public Session(JsonRpcClient client)
client.KeepAlive = true;
client.UserAgent = UserAgent;
client.WebProxy = Proxy;
client.JsonRpcVersion = JsonRpcVersion.v2;
client.AllowAutoRedirect = true;
JsonRpcClient = client;
}

[Obsolete("Use Session(string url) { Timeout = ... }; instead.")]
public Session(int timeout, string url)
: this(new JsonRpcClient(url))
{
JsonRpcClient.Timeout = timeout;
}

public Session(string url) :
this(new JsonRpcClient(url))
{
}

[Obsolete("Use Session(string host, int port) { Timeout = ... }; instead.")]
public Session(int timeout, string host, int port)
: this(timeout, GetUrl(host, port))
{
}

public Session(string host, int port)
: this(GetUrl(host, port))
{
Expand All @@ -100,23 +86,6 @@ public Session(string url, string opaqueRef)
SetupSessionDetails();
}

/// <summary>
/// Create a new Session instance, using the given instance and timeout. The connection details and Xen-API session handle will be
/// copied from the given instance, but a new connection will be created. Use this if you want a duplicate connection to a host,
/// for example when you need to cancel an operation that is blocking the primary connection.
/// </summary>
/// <param name="session"></param>
/// <param name="timeout"></param>
[Obsolete("Use Session(Session session) { Timeout = ... }; instead.")]
public Session(Session session, int timeout)
: this(session)
{
if (JsonRpcClient != null)
{
JsonRpcClient.Timeout = timeout;
}
}

/// <summary>
/// Create a new Session instance, using the given instance. The connection details
/// and Xen-API session handle will be copied from the given instance, but a new
Expand Down Expand Up @@ -175,6 +144,14 @@ private void SetAPIVersion()
Host host = Host.get_record(this, pool.master);
APIVersion = Helper.GetAPIVersion(host.API_version_major, host.API_version_minor);
}

if (JsonRpcClient != null)
{
if (APIVersion == API_Version.API_2_6)
JsonRpcClient.JsonRpcVersion = JsonRpcVersion.v1;
else if (APIVersion >= API_Version.API_2_8)
JsonRpcClient.JsonRpcVersion = JsonRpcVersion.v2;
}
}

private void CopyADFromSession(Session session)
Expand Down
28 changes: 4 additions & 24 deletions ocaml/sdk-gen/csharp/templates/ApiVersion.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@
*/

using System;
using System.Collections;
using System.Collections.Generic;


namespace XenAPI
{
Expand Down Expand Up @@ -63,8 +60,7 @@ namespace XenAPI
{
try
{
return (API_Version)Enum.Parse(typeof(API_Version),
string.Format("API_{0}_{1}", major, minor));
return (API_Version)Enum.Parse(typeof(API_Version), $"API_{major}_{minor}");
}
catch (ArgumentException)
{
Expand All @@ -82,30 +78,14 @@ namespace XenAPI
{
string[] tokens = version.Split('.');
int major, minor;
if (tokens.Length == 2 && int.TryParse(tokens[0], out major) && int.TryParse(tokens[1], out minor))
if (tokens.Length == 2 &&
int.TryParse(tokens[0], out major) &&
int.TryParse(tokens[1], out minor))
{
return GetAPIVersion(major, minor);
}
}
return API_Version.UNKNOWN;
}

/// <summary>
/// Return a positive number if the given session's API version is greater than the given
/// API_version, negative if it is less, and 0 if they are equal.
/// </summary>
internal static int APIVersionCompare(Session session, API_Version v)
{
return (int)session.APIVersion - (int)v;
}

/// <summary>
/// Return true if the given session's API version is greater than or equal to the given
/// API_version.
/// </summary>
internal static bool APIVersionMeets(Session session, API_Version v)
{
return APIVersionCompare(session, v) >= 0;
}
}
}
73 changes: 5 additions & 68 deletions ocaml/sdk-gen/powershell/autogen/src/CommonCmdletFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,11 @@

namespace Citrix.XenServer
{
class CommonCmdletFunctions
internal class CommonCmdletFunctions
{
private const string SessionsVariable = "global:Citrix.XenServer.Sessions";

private const string DefaultSessionVariable = "global:XenServer_Default_Session";

private const string KnownServerCertificatesFilePathVariable = "global:KnownServerCertificatesFilePath";

static CommonCmdletFunctions()
{
Session.UserAgent = string.Format("XenServerPSModule/{0}", Assembly.GetExecutingAssembly().GetName().Version);
Expand Down Expand Up @@ -78,72 +75,12 @@ internal static void SetDefaultXenSession(PSCmdlet cmdlet, Session session)
cmdlet.SessionState.PSVariable.Set(DefaultSessionVariable, session);
}

internal static string GetKnownServerCertificatesFilePathVariable(PSCmdlet cmdlet)
{
var knownCertificatesFilePathObject = cmdlet.SessionState.PSVariable.GetValue(KnownServerCertificatesFilePathVariable);
if (knownCertificatesFilePathObject is PSObject psObject)
return psObject.BaseObject as string;
return knownCertificatesFilePathObject?.ToString() ?? string.Empty;
}

internal static string GetUrl(string hostname, int port)
{
return string.Format("{0}://{1}:{2}", port == 80 ? "http" : "https", hostname, port);
}

public static Dictionary<string, string> LoadCertificates(PSCmdlet cmdlet)
{
Dictionary<string, string> certificates = new Dictionary<string, string>();
var knownServerCertificatesFilePath = GetKnownServerCertificatesFilePathVariable(cmdlet);

if (File.Exists(knownServerCertificatesFilePath))
{
XmlDocument doc = new XmlDocument();
doc.Load(knownServerCertificatesFilePath);

foreach (XmlNode node in doc.GetElementsByTagName("certificate"))
{
XmlAttribute hostAtt = node.Attributes?["hostname"];
XmlAttribute fngprtAtt = node.Attributes?["fingerprint"];

if (hostAtt != null && fngprtAtt != null)
certificates[hostAtt.Value] = fngprtAtt.Value;
}
}

return certificates;
}

public static void SaveCertificates(PSCmdlet cmdlet, Dictionary<string, string> certificates)
{
var knownServerCertificatesFilePath = GetKnownServerCertificatesFilePathVariable(cmdlet);
string dirName = Path.GetDirectoryName(knownServerCertificatesFilePath);

if (!Directory.Exists(dirName))
Directory.CreateDirectory(dirName);

XmlDocument doc = new XmlDocument();
XmlDeclaration decl = doc.CreateXmlDeclaration("1.0", "utf-8", null);
doc.AppendChild(decl);
XmlNode node = doc.CreateElement("certificates");

foreach (KeyValuePair<string, string> cert in certificates)
{
XmlNode certNode = doc.CreateElement("certificate");
XmlAttribute hostname = doc.CreateAttribute("hostname");
XmlAttribute fingerprint = doc.CreateAttribute("fingerprint");
hostname.Value = cert.Key;
fingerprint.Value = cert.Value;
certNode.Attributes?.Append(hostname);
certNode.Attributes?.Append(fingerprint);
node.AppendChild(certNode);
}

doc.AppendChild(node);
doc.Save(knownServerCertificatesFilePath);
return $"{(port == 80 ? "http" : "https")}://{hostname}:{port}";
}

public static string FingerprintPrettyString(string fingerprint)
internal static string FingerprintPrettyString(string fingerprint)
{
List<string> pairs = new List<string>();
while (fingerprint.Length > 1)
Expand All @@ -157,7 +94,7 @@ public static string FingerprintPrettyString(string fingerprint)
return string.Join(":", pairs.ToArray());
}

public static Dictionary<T, S> ConvertHashTableToDictionary<T, S>(Hashtable tbl)
internal static Dictionary<T, S> ConvertHashTableToDictionary<T, S>(Hashtable tbl)
{
if (tbl == null)
return null;
Expand All @@ -169,7 +106,7 @@ public static Dictionary<T, S> ConvertHashTableToDictionary<T, S>(Hashtable tbl)
return dict;
}

public static Hashtable ConvertDictionaryToHashtable<T, S>(Dictionary<T, S> dict)
internal static Hashtable ConvertDictionaryToHashtable<T, S>(Dictionary<T, S> dict)
{
if (dict == null)
return null;
Expand Down
83 changes: 71 additions & 12 deletions ocaml/sdk-gen/powershell/autogen/src/Connect-XenServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,24 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using System.Net;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using XenAPI;

namespace Citrix.XenServer.Commands
{
[Cmdlet("Connect", "XenServer")]
public class ConnectXenServerCommand : PSCmdlet
{
private const string CertificatesPathVariable = "global:KnownServerCertificatesFilePath";

private readonly object _certificateValidationLock = new object();

public ConnectXenServerCommand()
Expand Down Expand Up @@ -214,7 +218,10 @@ protected override void ProcessRecord()
{
if (ShouldContinue(ex.Message, ex.Caption))
{
AddCertificate(ex.Hostname, ex.Fingerprint);
var certPath = GetCertificatesPath();
var certificates = LoadCertificates(certPath);
certificates[ex.Hostname] = ex.Fingerprint;
SaveCertificates(certPath, certificates);
i--;
continue;
}
Expand Down Expand Up @@ -254,13 +261,6 @@ protected override void ProcessRecord()
WriteObject(newSessions.Values, true);
}

private void AddCertificate(string hostname, string fingerprint)
{
var certificates = CommonCmdletFunctions.LoadCertificates(this);
certificates[hostname] = fingerprint;
CommonCmdletFunctions.SaveCertificates(this, certificates);
}

private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
Expand All @@ -277,11 +277,11 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat

bool trusted = VerifyInAllStores(new X509Certificate2(certificate));

var certificates = CommonCmdletFunctions.LoadCertificates(this);
var certPath = GetCertificatesPath();
var certificates = LoadCertificates(certPath);

if (certificates.ContainsKey(hostname))
if (certificates.TryGetValue(hostname, out var fingerprintOld))
{
string fingerprintOld = certificates[hostname];
if (fingerprintOld == fingerprint)
return true;

Expand All @@ -295,7 +295,7 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat
}

certificates[hostname] = fingerprint;
CommonCmdletFunctions.SaveCertificates(this, certificates);
SaveCertificates(certPath, certificates);
return true;
}
}
Expand All @@ -312,6 +312,65 @@ private bool VerifyInAllStores(X509Certificate2 certificate2)
return false;
}
}

private string GetCertificatesPath()
{
var certPathObject = SessionState.PSVariable.GetValue(CertificatesPathVariable);

return certPathObject is PSObject psObject
? psObject.BaseObject as string
: certPathObject?.ToString() ?? string.Empty;
}

private Dictionary<string, string> LoadCertificates(string certPath)
{
var certificates = new Dictionary<string, string>();

if (File.Exists(certPath))
{
var doc = new XmlDocument();
doc.Load(certPath);

foreach (XmlNode node in doc.GetElementsByTagName("certificate"))
{
var hostAtt = node.Attributes?["hostname"];
var fngprtAtt = node.Attributes?["fingerprint"];

if (hostAtt != null && fngprtAtt != null)
certificates[hostAtt.Value] = fngprtAtt.Value;
}
}

return certificates;
}

private void SaveCertificates(string certPath, Dictionary<string, string> certificates)
{
string dirName = Path.GetDirectoryName(certPath);

if (!Directory.Exists(dirName))
Directory.CreateDirectory(dirName);

XmlDocument doc = new XmlDocument();
XmlDeclaration decl = doc.CreateXmlDeclaration("1.0", "utf-8", null);
doc.AppendChild(decl);
XmlNode node = doc.CreateElement("certificates");

foreach (KeyValuePair<string, string> cert in certificates)
{
XmlNode certNode = doc.CreateElement("certificate");
XmlAttribute hostname = doc.CreateAttribute("hostname");
XmlAttribute fingerprint = doc.CreateAttribute("fingerprint");
hostname.Value = cert.Key;
fingerprint.Value = cert.Value;
certNode.Attributes?.Append(hostname);
certNode.Attributes?.Append(fingerprint);
node.AppendChild(certNode);
}

doc.AppendChild(node);
doc.Save(certPath);
}
}

internal abstract class CertificateValidationException : Exception
Expand Down

0 comments on commit 8c9b754

Please sign in to comment.