Files
ps.ipam/classlib/Helpers/RequestHelper.cs

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
}