442 lines
15 KiB
C#
442 lines
15 KiB
C#
namespace PS.IPAM.Helpers;
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Management.Automation;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
/// <summary>
|
|
/// Helper class for making HTTP requests to the phpIPAM API.
|
|
/// </summary>
|
|
public static class RequestHelper
|
|
{
|
|
/// <summary>
|
|
/// Handler for testing - allows injecting a mock HTTP handler.
|
|
/// </summary>
|
|
public static HttpMessageHandler? TestHttpHandler { get; set; }
|
|
|
|
/// <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)
|
|
{
|
|
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)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var responseContent = await response.Content.ReadAsStringAsync();
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
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 InvalidOperationException("No session available!");
|
|
}
|
|
|
|
var status = SessionManager.GetSessionStatus();
|
|
if (status == SessionStatus.Valid)
|
|
{
|
|
// Just refresh the token
|
|
await InvokeRequestAsync(new HttpMethod("PATCH"), ApiController.User);
|
|
return;
|
|
}
|
|
|
|
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)}/";
|
|
}
|
|
|
|
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:
|
|
client.DefaultRequestHeaders.Add("token", session.Token);
|
|
break;
|
|
case AuthType.Token:
|
|
client.DefaultRequestHeaders.Add("phpipam-token", session.Token);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static async Task<HttpResponseMessage?> SendRequestAsync(
|
|
HttpClient client,
|
|
HttpMethod method,
|
|
string uri,
|
|
object? parameters)
|
|
{
|
|
try
|
|
{
|
|
if (method == HttpMethod.Get)
|
|
{
|
|
return await client.GetAsync(uri);
|
|
}
|
|
|
|
if (method == HttpMethod.Delete)
|
|
{
|
|
return await client.DeleteAsync(uri);
|
|
}
|
|
|
|
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
|
|
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
|
|
|
if (method == HttpMethod.Post)
|
|
{
|
|
return await client.PostAsync(uri, content);
|
|
}
|
|
|
|
// PATCH, PUT, etc.
|
|
var request = new HttpRequestMessage(method, uri) { Content = content };
|
|
return await client.SendAsync(request);
|
|
}
|
|
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var data = jsonResponse.data;
|
|
if (data is JArray array)
|
|
{
|
|
return array.Select(item => ConvertSingleObject(item, modelType)).ToList();
|
|
}
|
|
|
|
return ConvertSingleObject(data, modelType);
|
|
}
|
|
|
|
private static object? ConvertSingleObject(dynamic item, ModelType modelType)
|
|
{
|
|
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().Where(p => p.Name.StartsWith("custom_")))
|
|
{
|
|
customFields[prop.Name] = prop.Value?.ToObject<object>() ?? string.Empty;
|
|
}
|
|
return customFields.Count > 0 ? customFields : null;
|
|
}
|
|
|
|
#region Model Factory Methods
|
|
|
|
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 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 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 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 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 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 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 Domain CreateDomain(JObject obj) => new(
|
|
obj["id"]?.ToObject<int>() ?? 0,
|
|
obj["name"]?.ToString() ?? "",
|
|
obj["description"]?.ToString() ?? "",
|
|
obj["sections"]?.ToString() ?? ""
|
|
);
|
|
|
|
#endregion
|
|
}
|