Refactor IPAM model classes to use records for Address, Subnetwork, Vlan, Vrf, Section, Tag, Domain, Nameserver, and Session; enhance documentation and implement value equality for records.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
namespace PS.IPAM.Helpers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -9,135 +10,259 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PS.IPAM;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for making HTTP requests to the phpIPAM API.
|
||||
/// </summary>
|
||||
public static class RequestHelper
|
||||
{
|
||||
// Handler for testing - allows injecting a mock HTTP handler
|
||||
/// <summary>
|
||||
/// Handler for testing - allows injecting a mock HTTP handler.
|
||||
/// </summary>
|
||||
public static HttpMessageHandler? TestHttpHandler { get; set; }
|
||||
|
||||
public static async Task<object?> InvokeRequest(
|
||||
string method,
|
||||
controllers controller,
|
||||
types? type = null,
|
||||
subcontrollers? subController = null,
|
||||
/// <summary>
|
||||
/// Invokes an HTTP request to the phpIPAM API.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method (GET, POST, PATCH, DELETE).</param>
|
||||
/// <param name="controller">The API controller to call.</param>
|
||||
/// <param name="modelType">The expected model type for response conversion.</param>
|
||||
/// <param name="subController">Optional sub-controller for nested endpoints.</param>
|
||||
/// <param name="parameters">Optional request body parameters.</param>
|
||||
/// <param name="identifiers">Optional path identifiers.</param>
|
||||
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
|
||||
/// <returns>The deserialized response data, or null if not found.</returns>
|
||||
public static async Task<object?> InvokeRequestAsync(
|
||||
HttpMethod method,
|
||||
ApiController controller,
|
||||
ModelType? modelType = null,
|
||||
ApiSubController? subController = null,
|
||||
object? parameters = null,
|
||||
string[]? identifiers = null,
|
||||
bool ignoreSsl = false)
|
||||
{
|
||||
var tokenStatus = SessionManager.TestSession();
|
||||
if (tokenStatus == "NoToken")
|
||||
EnsureValidSession();
|
||||
|
||||
var session = SessionManager.CurrentSession!;
|
||||
var uri = BuildUri(session, controller, subController, identifiers);
|
||||
|
||||
using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler);
|
||||
ConfigureClient(client, session);
|
||||
|
||||
var response = await SendRequestAsync(client, method, uri, parameters);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new Exception("No session available!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tokenStatus == "Expired")
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await UpdateSession();
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw new HttpRequestException($"Request failed with status {response.StatusCode}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ParseResponse(responseContent, modelType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overload for backward compatibility using string method names.
|
||||
/// </summary>
|
||||
public static Task<object?> InvokeRequest(
|
||||
string method,
|
||||
ApiController controller,
|
||||
ModelType? modelType = null,
|
||||
ApiSubController? subController = null,
|
||||
object? parameters = null,
|
||||
string[]? identifiers = null,
|
||||
bool ignoreSsl = false)
|
||||
{
|
||||
var httpMethod = method.ToUpperInvariant() switch
|
||||
{
|
||||
"GET" => HttpMethod.Get,
|
||||
"POST" => HttpMethod.Post,
|
||||
"PATCH" => new HttpMethod("PATCH"),
|
||||
"DELETE" => HttpMethod.Delete,
|
||||
"PUT" => HttpMethod.Put,
|
||||
_ => throw new ArgumentException($"Unsupported HTTP method: {method}", nameof(method))
|
||||
};
|
||||
|
||||
return InvokeRequestAsync(httpMethod, controller, modelType, subController, parameters, identifiers, ignoreSsl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes an expired session.
|
||||
/// </summary>
|
||||
public static async Task RefreshSessionAsync()
|
||||
{
|
||||
var session = SessionManager.CurrentSession;
|
||||
if (session == null)
|
||||
{
|
||||
throw new Exception("No session available!");
|
||||
throw new InvalidOperationException("No session available!");
|
||||
}
|
||||
|
||||
var uri = $"{session.URL}/api/{session.AppID}/{controller}";
|
||||
if (subController != null)
|
||||
var status = SessionManager.GetSessionStatus();
|
||||
if (status == SessionStatus.Valid)
|
||||
{
|
||||
uri += $"/{subController}";
|
||||
// Just refresh the token
|
||||
await InvokeRequestAsync(new HttpMethod("PATCH"), ApiController.User);
|
||||
return;
|
||||
}
|
||||
if (identifiers != null && identifiers.Length > 0)
|
||||
|
||||
if (status == SessionStatus.Expired && session.Credentials is PSCredential creds)
|
||||
{
|
||||
await SessionManager.CreateSessionWithCredentialsAsync(
|
||||
session.URL,
|
||||
session.AppID,
|
||||
creds,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureValidSession()
|
||||
{
|
||||
var status = SessionManager.GetSessionStatus();
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case SessionStatus.NoSession:
|
||||
throw new InvalidOperationException("No session available!");
|
||||
|
||||
case SessionStatus.Expired:
|
||||
RefreshSessionAsync().GetAwaiter().GetResult();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildUri(
|
||||
Session session,
|
||||
ApiController controller,
|
||||
ApiSubController? subController,
|
||||
string[]? identifiers)
|
||||
{
|
||||
var controllerName = GetControllerName(controller);
|
||||
var uri = $"{session.URL}/api/{session.AppID}/{controllerName}";
|
||||
|
||||
if (subController.HasValue)
|
||||
{
|
||||
uri += $"/{GetSubControllerName(subController.Value)}";
|
||||
}
|
||||
|
||||
if (identifiers is { Length: > 0 })
|
||||
{
|
||||
uri += $"/{string.Join("/", identifiers)}/";
|
||||
}
|
||||
|
||||
using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler);
|
||||
return uri;
|
||||
}
|
||||
|
||||
private static string GetControllerName(ApiController controller) => controller switch
|
||||
{
|
||||
ApiController.User => "user",
|
||||
ApiController.Vlan => "vlan",
|
||||
ApiController.Subnets => "subnets",
|
||||
ApiController.Addresses => "addresses",
|
||||
ApiController.Sections => "sections",
|
||||
ApiController.Vrf => "vrf",
|
||||
ApiController.L2Domains => "l2domains",
|
||||
ApiController.Tools => "tools",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(controller))
|
||||
};
|
||||
|
||||
private static string GetSubControllerName(ApiSubController subController) => subController switch
|
||||
{
|
||||
ApiSubController.Nameservers => "nameservers",
|
||||
ApiSubController.Tags => "tags",
|
||||
ApiSubController.Devices => "devices",
|
||||
ApiSubController.DeviceTypes => "device_types",
|
||||
ApiSubController.Vlans => "vlans",
|
||||
ApiSubController.Vrfs => "vrfs",
|
||||
ApiSubController.ScanAgents => "scanagents",
|
||||
ApiSubController.Locations => "locations",
|
||||
ApiSubController.Nat => "nat",
|
||||
ApiSubController.Racks => "racks",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(subController))
|
||||
};
|
||||
|
||||
private static void ConfigureClient(HttpClient client, Session session)
|
||||
{
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
switch (session.AuthType)
|
||||
{
|
||||
case AuthType.credentials:
|
||||
case AuthType.Credentials:
|
||||
client.DefaultRequestHeaders.Add("token", session.Token);
|
||||
break;
|
||||
case AuthType.token:
|
||||
case AuthType.Token:
|
||||
client.DefaultRequestHeaders.Add("phpipam-token", session.Token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponseMessage? response = null;
|
||||
private static async Task<HttpResponseMessage?> SendRequestAsync(
|
||||
HttpClient client,
|
||||
HttpMethod method,
|
||||
string uri,
|
||||
object? parameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (method == "GET")
|
||||
if (method == HttpMethod.Get)
|
||||
{
|
||||
response = await client.GetAsync(uri);
|
||||
}
|
||||
else if (method == "POST")
|
||||
{
|
||||
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
response = await client.PostAsync(uri, content);
|
||||
}
|
||||
else if (method == "PATCH")
|
||||
{
|
||||
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
var request = new HttpRequestMessage(new HttpMethod("PATCH"), uri)
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
response = await client.SendAsync(request);
|
||||
}
|
||||
else if (method == "DELETE")
|
||||
{
|
||||
response = await client.DeleteAsync(uri);
|
||||
return await client.GetAsync(uri);
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
if (method == HttpMethod.Delete)
|
||||
{
|
||||
return null;
|
||||
return await client.DeleteAsync(uri);
|
||||
}
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
if (method == HttpMethod.Post)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw new HttpRequestException($"Request failed with status {response.StatusCode}");
|
||||
return await client.PostAsync(uri, content);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
|
||||
if (jsonResponse == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.HasValue)
|
||||
{
|
||||
return ConvertToTypedObjects(jsonResponse, type.Value);
|
||||
}
|
||||
|
||||
return jsonResponse.data;
|
||||
// PATCH, PUT, etc.
|
||||
var request = new HttpRequestMessage(method, uri) { Content = content };
|
||||
return await client.SendAsync(request);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
|
||||
{
|
||||
if (ex.Message.Contains("404"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static object? ConvertToTypedObjects(dynamic jsonResponse, types type)
|
||||
private static object? ParseResponse(string responseContent, ModelType? modelType)
|
||||
{
|
||||
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
|
||||
if (jsonResponse == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!modelType.HasValue)
|
||||
{
|
||||
return jsonResponse.data;
|
||||
}
|
||||
|
||||
return ConvertToTypedObjects(jsonResponse, modelType.Value);
|
||||
}
|
||||
|
||||
private static object? ConvertToTypedObjects(dynamic jsonResponse, ModelType modelType)
|
||||
{
|
||||
if (jsonResponse?.data == null)
|
||||
{
|
||||
@@ -147,228 +272,170 @@ public static class RequestHelper
|
||||
var data = jsonResponse.data;
|
||||
if (data is JArray array)
|
||||
{
|
||||
return array.Select(item => ConvertSingleObject(item, type)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConvertSingleObject(data, type);
|
||||
return array.Select(item => ConvertSingleObject(item, modelType)).ToList();
|
||||
}
|
||||
|
||||
return ConvertSingleObject(data, modelType);
|
||||
}
|
||||
|
||||
private static object? ConvertSingleObject(dynamic item, types type)
|
||||
private static object? ConvertSingleObject(dynamic item, ModelType modelType)
|
||||
{
|
||||
var jobject = item as JObject;
|
||||
if (jobject == null)
|
||||
if (item is not JObject jobject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var customFields = ExtractCustomFields(jobject);
|
||||
|
||||
return modelType switch
|
||||
{
|
||||
ModelType.Address => CreateAddress(jobject, customFields),
|
||||
ModelType.Vlan => CreateVlan(jobject, customFields),
|
||||
ModelType.Subnetwork => CreateSubnetwork(jobject, customFields),
|
||||
ModelType.Vrf => CreateVrf(jobject, customFields),
|
||||
ModelType.Section => CreateSection(jobject),
|
||||
ModelType.Tag => CreateTag(jobject),
|
||||
ModelType.Nameserver => CreateNameserver(jobject),
|
||||
ModelType.Domain => CreateDomain(jobject),
|
||||
_ => jobject.ToObject<object>()
|
||||
};
|
||||
}
|
||||
|
||||
private static Dictionary<string, object>? ExtractCustomFields(JObject jobject)
|
||||
{
|
||||
var customFields = new Dictionary<string, object>();
|
||||
foreach (var prop in jobject.Properties())
|
||||
foreach (var prop in jobject.Properties().Where(p => p.Name.StartsWith("custom_")))
|
||||
{
|
||||
if (prop.Name.StartsWith("custom_"))
|
||||
{
|
||||
customFields[prop.Name] = prop.Value?.ToObject<object>() ?? new object();
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case types.Address:
|
||||
return CreateAddress(jobject, customFields);
|
||||
case types.Vlan:
|
||||
return CreateVlan(jobject, customFields);
|
||||
case types.Subnetwork:
|
||||
return CreateSubnetwork(jobject, customFields);
|
||||
case types.Vrf:
|
||||
return CreateVrf(jobject, customFields);
|
||||
case types.Section:
|
||||
return CreateSection(jobject);
|
||||
case types.Tag:
|
||||
return CreateTag(jobject);
|
||||
case types.Nameserver:
|
||||
return CreateNameserver(jobject);
|
||||
case types.Domain:
|
||||
return CreateDomain(jobject);
|
||||
default:
|
||||
return jobject.ToObject<object>();
|
||||
customFields[prop.Name] = prop.Value?.ToObject<object>() ?? string.Empty;
|
||||
}
|
||||
return customFields.Count > 0 ? customFields : null;
|
||||
}
|
||||
|
||||
private static Address CreateAddress(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
return new Address(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["subnetId"]?.ToObject<int>() ?? 0,
|
||||
jobject["ip"]?.ToString() ?? "",
|
||||
jobject["is_gateway"]?.ToObject<bool>() ?? false,
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["hostname"]?.ToString() ?? "",
|
||||
jobject["mac"]?.ToString() ?? "",
|
||||
jobject["owner"]?.ToString() ?? "",
|
||||
jobject["tag"]?.ToObject<int>() ?? 0,
|
||||
jobject["deviceId"]?.ToObject<int>() ?? 0,
|
||||
jobject["location"]?.ToString() ?? "",
|
||||
jobject["port"]?.ToString() ?? "",
|
||||
jobject["note"]?.ToString() ?? "",
|
||||
jobject["lastSeen"]?.ToObject<DateTime?>(),
|
||||
jobject["excludePing"]?.ToObject<bool>() ?? false,
|
||||
jobject["PTRignore"]?.ToObject<bool>() ?? false,
|
||||
jobject["PTR"]?.ToObject<int>() ?? 0,
|
||||
jobject["firewallAddressObject"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["customer_id"]?.ToObject<int>() ?? 0,
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
#region Model Factory Methods
|
||||
|
||||
private static Vlan CreateVlan(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
return new Vlan(
|
||||
jobject["vlanId"]?.ToObject<int>() ?? 0,
|
||||
jobject["domainId"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["number"]?.ToObject<int>() ?? 0,
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["customer_id"]?.ToObject<int>() ?? 0,
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
private static Address CreateAddress(JObject obj, Dictionary<string, object>? customFields) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["subnetId"]?.ToObject<int>() ?? 0,
|
||||
obj["ip"]?.ToString() ?? "",
|
||||
obj["is_gateway"]?.ToObject<bool>() ?? false,
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["hostname"]?.ToString() ?? "",
|
||||
obj["mac"]?.ToString() ?? "",
|
||||
obj["owner"]?.ToString() ?? "",
|
||||
obj["tag"]?.ToObject<int>() ?? 0,
|
||||
obj["deviceId"]?.ToObject<int>() ?? 0,
|
||||
obj["location"]?.ToString() ?? "",
|
||||
obj["port"]?.ToString() ?? "",
|
||||
obj["note"]?.ToString() ?? "",
|
||||
obj["lastSeen"]?.ToObject<DateTime?>(),
|
||||
obj["excludePing"]?.ToObject<bool>() ?? false,
|
||||
obj["PTRignore"]?.ToObject<bool>() ?? false,
|
||||
obj["PTR"]?.ToObject<int>() ?? 0,
|
||||
obj["firewallAddressObject"]?.ToString() ?? "",
|
||||
obj["editDate"]?.ToObject<DateTime?>(),
|
||||
obj["customer_id"]?.ToObject<int>() ?? 0,
|
||||
customFields
|
||||
);
|
||||
|
||||
private static Subnetwork CreateSubnetwork(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
var props = jobject.Properties().ToList();
|
||||
return new Subnetwork(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["subnet"]?.ToString() ?? "",
|
||||
jobject["mask"]?.ToObject<int>() ?? 0,
|
||||
jobject["sectionId"]?.ToObject<int>() ?? 0,
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["linked_subnet"]?.ToString() ?? "",
|
||||
jobject["firewallAddressObject"]?.ToString() ?? "",
|
||||
jobject["vrfId"]?.ToObject<int>() ?? 0,
|
||||
jobject["masterSubnetId"]?.ToObject<int>() ?? 0,
|
||||
jobject["allowRequests"]?.ToObject<bool>() ?? false,
|
||||
jobject["vlanId"]?.ToObject<int>() ?? 0,
|
||||
jobject["showName"]?.ToObject<bool>() ?? false,
|
||||
jobject["deviceId"]?.ToObject<int>() ?? 0,
|
||||
jobject["permissions"]?.ToString() ?? "",
|
||||
jobject["pingSubnet"]?.ToObject<bool>() ?? false,
|
||||
jobject["discoverSubnet"]?.ToObject<bool>() ?? false,
|
||||
jobject["resolveDNS"]?.ToObject<bool>() ?? false,
|
||||
jobject["DNSrecursive"]?.ToObject<bool>() ?? false,
|
||||
jobject["DNSrecords"]?.ToObject<bool>() ?? false,
|
||||
jobject["nameserverId"]?.ToObject<int>() ?? 0,
|
||||
jobject["scanAgent"]?.ToObject<bool>() ?? false,
|
||||
jobject["isFolder"]?.ToObject<bool>() ?? false,
|
||||
jobject["isFull"]?.ToObject<bool>() ?? false,
|
||||
jobject["isPool"]?.ToObject<bool>() ?? false,
|
||||
jobject["state"]?.ToObject<int>() ?? 0,
|
||||
jobject["threshold"]?.ToObject<int>() ?? 0,
|
||||
jobject["location"]?.ToObject<int>() ?? 0,
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["lastScan"]?.ToObject<DateTime?>(),
|
||||
jobject["lastDiscovery"]?.ToObject<DateTime?>(),
|
||||
jobject["calculation"]?.ToObject<object>() ?? new object(),
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
private static Vlan CreateVlan(JObject obj, Dictionary<string, object>? customFields) => new(
|
||||
obj["vlanId"]?.ToObject<int>() ?? 0,
|
||||
obj["domainId"]?.ToObject<int>() ?? 0,
|
||||
obj["name"]?.ToString() ?? "",
|
||||
obj["number"]?.ToObject<int>() ?? 0,
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["editDate"]?.ToObject<DateTime?>(),
|
||||
obj["customer_id"]?.ToObject<int>() ?? 0,
|
||||
customFields
|
||||
);
|
||||
|
||||
private static Vrf CreateVrf(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
return new Vrf(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["rd"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["sections"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
private static Subnetwork CreateSubnetwork(JObject obj, Dictionary<string, object>? customFields) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["subnet"]?.ToString() ?? "",
|
||||
obj["mask"]?.ToObject<int>() ?? 0,
|
||||
obj["sectionId"]?.ToObject<int>() ?? 0,
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["linked_subnet"]?.ToString() ?? "",
|
||||
obj["firewallAddressObject"]?.ToString() ?? "",
|
||||
obj["vrfId"]?.ToObject<int>() ?? 0,
|
||||
obj["masterSubnetId"]?.ToObject<int>() ?? 0,
|
||||
obj["allowRequests"]?.ToObject<bool>() ?? false,
|
||||
obj["vlanId"]?.ToObject<int>() ?? 0,
|
||||
obj["showName"]?.ToObject<bool>() ?? false,
|
||||
obj["deviceId"]?.ToObject<int>() ?? 0,
|
||||
obj["permissions"]?.ToString() ?? "",
|
||||
obj["pingSubnet"]?.ToObject<bool>() ?? false,
|
||||
obj["discoverSubnet"]?.ToObject<bool>() ?? false,
|
||||
obj["resolveDNS"]?.ToObject<bool>() ?? false,
|
||||
obj["DNSrecursive"]?.ToObject<bool>() ?? false,
|
||||
obj["DNSrecords"]?.ToObject<bool>() ?? false,
|
||||
obj["nameserverId"]?.ToObject<int>() ?? 0,
|
||||
obj["scanAgent"]?.ToObject<bool>() ?? false,
|
||||
obj["isFolder"]?.ToObject<bool>() ?? false,
|
||||
obj["isFull"]?.ToObject<bool>() ?? false,
|
||||
obj["isPool"]?.ToObject<bool>() ?? false,
|
||||
obj["state"]?.ToObject<int>() ?? 0,
|
||||
obj["threshold"]?.ToObject<int>() ?? 0,
|
||||
obj["location"]?.ToObject<int>() ?? 0,
|
||||
obj["editDate"]?.ToObject<DateTime?>(),
|
||||
obj["lastScan"]?.ToObject<DateTime?>(),
|
||||
obj["lastDiscovery"]?.ToObject<DateTime?>(),
|
||||
obj["calculation"]?.ToObject<object>() ?? new object(),
|
||||
customFields
|
||||
);
|
||||
|
||||
private static Section CreateSection(JObject jobject)
|
||||
{
|
||||
return new Section(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["masterSection"]?.ToObject<int>() ?? 0,
|
||||
jobject["permissions"]?.ToString() ?? "",
|
||||
jobject["strictMode"]?.ToObject<bool>() ?? false,
|
||||
jobject["subnetOrdering"]?.ToString() ?? "",
|
||||
jobject["order"]?.ToObject<int>() ?? 0,
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["showSubnet"]?.ToObject<bool>() ?? false,
|
||||
jobject["showVlan"]?.ToObject<bool>() ?? false,
|
||||
jobject["showVRF"]?.ToObject<bool>() ?? false,
|
||||
jobject["showSupernetOnly"]?.ToObject<bool>() ?? false,
|
||||
jobject["DNS"]?.ToObject<int>() ?? 0
|
||||
);
|
||||
}
|
||||
private static Vrf CreateVrf(JObject obj, Dictionary<string, object>? customFields) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["name"]?.ToString() ?? "",
|
||||
obj["rd"]?.ToString() ?? "",
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["sections"]?.ToString() ?? "",
|
||||
obj["editDate"]?.ToObject<DateTime?>(),
|
||||
customFields
|
||||
);
|
||||
|
||||
private static Tag CreateTag(JObject jobject)
|
||||
{
|
||||
return new Tag(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["type"]?.ToString() ?? "",
|
||||
jobject["showtag"]?.ToObject<bool>() ?? false,
|
||||
jobject["bgcolor"]?.ToString() ?? "",
|
||||
jobject["fgcolor"]?.ToString() ?? "",
|
||||
jobject["compress"]?.ToString() ?? "",
|
||||
jobject["locked"]?.ToString() ?? "",
|
||||
jobject["updateTag"]?.ToObject<bool>() ?? false
|
||||
);
|
||||
}
|
||||
private static Section CreateSection(JObject obj) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["name"]?.ToString() ?? "",
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["masterSection"]?.ToObject<int>() ?? 0,
|
||||
obj["permissions"]?.ToString() ?? "",
|
||||
obj["strictMode"]?.ToObject<bool>() ?? false,
|
||||
obj["subnetOrdering"]?.ToString() ?? "",
|
||||
obj["order"]?.ToObject<int>() ?? 0,
|
||||
obj["editDate"]?.ToObject<DateTime?>(),
|
||||
obj["showSubnet"]?.ToObject<bool>() ?? false,
|
||||
obj["showVlan"]?.ToObject<bool>() ?? false,
|
||||
obj["showVRF"]?.ToObject<bool>() ?? false,
|
||||
obj["showSupernetOnly"]?.ToObject<bool>() ?? false,
|
||||
obj["DNS"]?.ToObject<int>() ?? 0
|
||||
);
|
||||
|
||||
private static Nameserver CreateNameserver(JObject jobject)
|
||||
{
|
||||
return new Nameserver(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["nameservers"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["permissions"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>()
|
||||
);
|
||||
}
|
||||
private static Tag CreateTag(JObject obj) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["type"]?.ToString() ?? "",
|
||||
obj["showtag"]?.ToObject<bool>() ?? false,
|
||||
obj["bgcolor"]?.ToString() ?? "",
|
||||
obj["fgcolor"]?.ToString() ?? "",
|
||||
obj["compress"]?.ToString() ?? "",
|
||||
obj["locked"]?.ToString() ?? "",
|
||||
obj["updateTag"]?.ToObject<bool>() ?? false
|
||||
);
|
||||
|
||||
private static Domain CreateDomain(JObject jobject)
|
||||
{
|
||||
return new Domain(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["sections"]?.ToString() ?? ""
|
||||
);
|
||||
}
|
||||
private static Nameserver CreateNameserver(JObject obj) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["name"]?.ToString() ?? "",
|
||||
obj["nameservers"]?.ToString() ?? "",
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["permissions"]?.ToString() ?? "",
|
||||
obj["editDate"]?.ToObject<DateTime?>()
|
||||
);
|
||||
|
||||
private static async Task UpdateSession()
|
||||
{
|
||||
var session = SessionManager.CurrentSession;
|
||||
if (session == null)
|
||||
{
|
||||
throw new Exception("No session available!");
|
||||
}
|
||||
private static Domain CreateDomain(JObject obj) => new(
|
||||
obj["id"]?.ToObject<int>() ?? 0,
|
||||
obj["name"]?.ToString() ?? "",
|
||||
obj["description"]?.ToString() ?? "",
|
||||
obj["sections"]?.ToString() ?? ""
|
||||
);
|
||||
|
||||
var tokenStatus = SessionManager.TestSession();
|
||||
if (tokenStatus == "Valid")
|
||||
{
|
||||
// Just refresh the token
|
||||
var result = await InvokeRequest("PATCH", controllers.user, null, null, null, null);
|
||||
// Token refresh doesn't return expires in the same format, so we'll skip updating expires
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenStatus == "Expired" && session.Credentials is PSCredential creds)
|
||||
{
|
||||
await SessionManager.CreateSessionWithCredentials(
|
||||
session.URL,
|
||||
session.AppID,
|
||||
creds,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,44 +1,60 @@
|
||||
namespace PS.IPAM.Helpers;
|
||||
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PS.IPAM;
|
||||
|
||||
/// <summary>
|
||||
/// Manages phpIPAM API sessions including creation, validation, and lifecycle.
|
||||
/// </summary>
|
||||
public static class SessionManager
|
||||
{
|
||||
private static Session? _currentSession;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current active session.
|
||||
/// </summary>
|
||||
public static Session? CurrentSession
|
||||
{
|
||||
get => _currentSession;
|
||||
set => _currentSession = value;
|
||||
}
|
||||
|
||||
public static string TestSession()
|
||||
/// <summary>
|
||||
/// Tests the current session status.
|
||||
/// </summary>
|
||||
/// <returns>The session status indicating validity or issues.</returns>
|
||||
public static SessionStatus GetSessionStatus()
|
||||
{
|
||||
if (_currentSession == null)
|
||||
{
|
||||
return "NoToken";
|
||||
return SessionStatus.NoSession;
|
||||
}
|
||||
|
||||
if (_currentSession.Expires == null)
|
||||
{
|
||||
return "Valid";
|
||||
return SessionStatus.Valid;
|
||||
}
|
||||
|
||||
if (_currentSession.Expires < DateTime.Now)
|
||||
{
|
||||
return "Expired";
|
||||
}
|
||||
|
||||
return "Valid";
|
||||
return _currentSession.Expires < DateTime.Now
|
||||
? SessionStatus.Expired
|
||||
: SessionStatus.Valid;
|
||||
}
|
||||
|
||||
public static async Task<Session> CreateSessionWithCredentials(
|
||||
/// <summary>
|
||||
/// Creates a new session using username/password credentials.
|
||||
/// </summary>
|
||||
/// <param name="url">The phpIPAM server URL.</param>
|
||||
/// <param name="appId">The API application ID.</param>
|
||||
/// <param name="credentials">The PowerShell credential object.</param>
|
||||
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
|
||||
/// <returns>The created session.</returns>
|
||||
public static async Task<Session> CreateSessionWithCredentialsAsync(
|
||||
string url,
|
||||
string appId,
|
||||
PSCredential credentials,
|
||||
@@ -46,7 +62,7 @@ public static class SessionManager
|
||||
{
|
||||
var uri = $"{url}/api/{appId}/user";
|
||||
var auth = Convert.ToBase64String(
|
||||
Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPassword(credentials)}"));
|
||||
Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPasswordString(credentials)}"));
|
||||
|
||||
using var client = CreateHttpClient(ignoreSsl);
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
@@ -58,14 +74,15 @@ public static class SessionManager
|
||||
|
||||
if (jsonResponse?.success != true)
|
||||
{
|
||||
throw new Exception(jsonResponse?.error?.ToString() ?? "Failed to create session");
|
||||
throw new InvalidOperationException(
|
||||
jsonResponse?.error?.ToString() ?? "Failed to create session");
|
||||
}
|
||||
|
||||
var token = jsonResponse.data.token.ToString();
|
||||
var expires = DateTime.Parse(jsonResponse.data.expires.ToString());
|
||||
|
||||
_currentSession = new Session(
|
||||
AuthType.credentials,
|
||||
AuthType.Credentials,
|
||||
token,
|
||||
appId,
|
||||
url,
|
||||
@@ -76,13 +93,17 @@ public static class SessionManager
|
||||
return _currentSession;
|
||||
}
|
||||
|
||||
public static Session CreateSessionWithToken(
|
||||
string url,
|
||||
string appId,
|
||||
string token)
|
||||
/// <summary>
|
||||
/// Creates a new session using a static API token.
|
||||
/// </summary>
|
||||
/// <param name="url">The phpIPAM server URL.</param>
|
||||
/// <param name="appId">The API application ID.</param>
|
||||
/// <param name="token">The API token.</param>
|
||||
/// <returns>The created session.</returns>
|
||||
public static Session CreateSessionWithToken(string url, string appId, string token)
|
||||
{
|
||||
_currentSession = new Session(
|
||||
AuthType.token,
|
||||
AuthType.Token,
|
||||
token,
|
||||
appId,
|
||||
url,
|
||||
@@ -93,24 +114,20 @@ public static class SessionManager
|
||||
return _currentSession;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the current session and clears session data.
|
||||
/// </summary>
|
||||
public static void CloseSession()
|
||||
{
|
||||
_currentSession = null;
|
||||
}
|
||||
|
||||
private static string GetPassword(PSCredential credential)
|
||||
{
|
||||
var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(credential.Password);
|
||||
try
|
||||
{
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an HttpClient with optional SSL bypass.
|
||||
/// </summary>
|
||||
/// <param name="ignoreSsl">Whether to ignore SSL certificate errors.</param>
|
||||
/// <param name="handler">Optional custom message handler for testing.</param>
|
||||
/// <returns>A configured HttpClient instance.</returns>
|
||||
public static HttpClient CreateHttpClient(bool ignoreSsl = false, HttpMessageHandler? handler = null)
|
||||
{
|
||||
if (handler != null)
|
||||
@@ -122,10 +139,27 @@ public static class SessionManager
|
||||
{
|
||||
var sslHandler = new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
|
||||
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
||||
};
|
||||
return new HttpClient(sslHandler);
|
||||
}
|
||||
|
||||
return new HttpClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the plain text password from a PSCredential object.
|
||||
/// </summary>
|
||||
private static string GetPasswordString(PSCredential credential)
|
||||
{
|
||||
var ptr = Marshal.SecureStringToBSTR(credential.Password);
|
||||
try
|
||||
{
|
||||
return Marshal.PtrToStringBSTR(ptr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ZeroFreeBSTR(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user