296 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace AmtScanner.Api.Services;
/// <summary>
/// Guacamole 远程桌面服务
/// </summary>
public class GuacamoleService
{
private readonly ILogger<GuacamoleService> _logger;
private readonly HttpClient _httpClient;
private readonly IConfiguration _config;
private string? _authToken;
private DateTime _tokenExpiry = DateTime.MinValue;
public GuacamoleService(ILogger<GuacamoleService> logger, IHttpClientFactory httpClientFactory, IConfiguration config)
{
_logger = logger;
_httpClient = httpClientFactory.CreateClient("Guacamole");
_config = config;
}
/// <summary>
/// 获取 Guacamole 认证 Token
/// </summary>
public async Task<string?> GetAuthTokenAsync()
{
// 如果 token 还有效,直接返回
if (!string.IsNullOrEmpty(_authToken) && DateTime.Now < _tokenExpiry)
{
return _authToken;
}
try
{
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
var username = _config["Guacamole:Username"] ?? "guacadmin";
var password = _config["Guacamole:Password"] ?? "guacadmin";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password)
});
var response = await _httpClient.PostAsync($"{baseUrl}/api/tokens", content);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<JsonElement>(json);
_authToken = result.GetProperty("authToken").GetString();
_tokenExpiry = DateTime.Now.AddMinutes(30); // Token 有效期约 30 分钟
_logger.LogInformation("Guacamole 认证成功");
return _authToken;
}
else
{
_logger.LogError("Guacamole 认证失败: {Status}", response.StatusCode);
return null;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "获取 Guacamole Token 失败");
return null;
}
}
/// <summary>
/// 创建 RDP 连接
/// </summary>
public async Task<string?> CreateRdpConnectionAsync(string name, string hostname, string username, string password, int port = 3389)
{
var token = await GetAuthTokenAsync();
if (string.IsNullOrEmpty(token)) return null;
try
{
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
var connection = new
{
parentIdentifier = "ROOT",
name = name,
protocol = "rdp",
parameters = new
{
hostname = hostname,
port = port.ToString(),
username = username,
password = password,
security = "any",
ignoreCert = "true",
enableDrive = "true",
createDrivePath = "true"
},
attributes = new
{
maxConnections = "",
maxConnectionsPerUser = ""
}
};
var json = JsonSerializer.Serialize(connection);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(
$"{baseUrl}/api/session/data/{dataSource}/connections?token={token}",
content);
if (response.IsSuccessStatusCode)
{
var resultJson = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<JsonElement>(resultJson);
var connectionId = result.GetProperty("identifier").GetString();
_logger.LogInformation("创建 RDP 连接成功: {Id}", connectionId);
return connectionId;
}
else
{
var error = await response.Content.ReadAsStringAsync();
_logger.LogError("创建 RDP 连接失败: {Status} - {Error}", response.StatusCode, error);
return null;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "创建 RDP 连接异常");
return null;
}
}
/// <summary>
/// 创建 VNC 连接
/// </summary>
public async Task<string?> CreateVncConnectionAsync(string name, string hostname, string password, int port = 5900)
{
var token = await GetAuthTokenAsync();
if (string.IsNullOrEmpty(token)) return null;
try
{
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
var connection = new
{
parentIdentifier = "ROOT",
name = name,
protocol = "vnc",
parameters = new
{
hostname = hostname,
port = port.ToString(),
password = password
},
attributes = new
{
maxConnections = "",
maxConnectionsPerUser = ""
}
};
var json = JsonSerializer.Serialize(connection);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(
$"{baseUrl}/api/session/data/{dataSource}/connections?token={token}",
content);
if (response.IsSuccessStatusCode)
{
var resultJson = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<JsonElement>(resultJson);
var connectionId = result.GetProperty("identifier").GetString();
_logger.LogInformation("创建 VNC 连接成功: {Id}", connectionId);
return connectionId;
}
else
{
var error = await response.Content.ReadAsStringAsync();
_logger.LogError("创建 VNC 连接失败: {Status} - {Error}", response.StatusCode, error);
return null;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "创建 VNC 连接异常");
return null;
}
}
/// <summary>
/// 删除连接
/// </summary>
public async Task<bool> DeleteConnectionAsync(string connectionId)
{
var token = await GetAuthTokenAsync();
if (string.IsNullOrEmpty(token)) return false;
try
{
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
var response = await _httpClient.DeleteAsync(
$"{baseUrl}/api/session/data/{dataSource}/connections/{connectionId}?token={token}");
if (response.IsSuccessStatusCode)
{
_logger.LogInformation("删除连接成功: {Id}", connectionId);
return true;
}
else
{
_logger.LogError("删除连接失败: {Status}", response.StatusCode);
return false;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "删除连接异常");
return false;
}
}
/// <summary>
/// 获取连接 URL用于 iframe 嵌入)
/// </summary>
public async Task<string?> GetConnectionUrlAsync(string connectionId)
{
var token = await GetAuthTokenAsync();
if (string.IsNullOrEmpty(token)) return null;
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
// 构建客户端连接 URL
// 格式: BASE_URL/#/client/BASE64(connectionId + '\0' + 'c' + '\0' + dataSource)
var clientId = $"{connectionId}\0c\0{dataSource}";
var encodedClientId = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientId));
return $"{baseUrl}/#/client/{encodedClientId}?token={token}";
}
/// <summary>
/// 获取所有连接
/// </summary>
public async Task<List<GuacamoleConnection>> GetConnectionsAsync()
{
var token = await GetAuthTokenAsync();
if (string.IsNullOrEmpty(token)) return new List<GuacamoleConnection>();
try
{
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
var response = await _httpClient.GetAsync(
$"{baseUrl}/api/session/data/{dataSource}/connections?token={token}");
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var connections = JsonSerializer.Deserialize<Dictionary<string, GuacamoleConnection>>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return connections?.Values.ToList() ?? new List<GuacamoleConnection>();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "获取连接列表失败");
}
return new List<GuacamoleConnection>();
}
}
public class GuacamoleConnection
{
public string Identifier { get; set; } = "";
public string Name { get; set; } = "";
public string Protocol { get; set; } = "";
public string ParentIdentifier { get; set; } = "";
}