Initial import into Gitea
This commit is contained in:
36
Management/Services/GraphService.cs
Normal file
36
Management/Services/GraphService.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Azure.Identity;
|
||||
using Microsoft.Graph;
|
||||
|
||||
namespace Management.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a Microsoft.Graph client authenticated with app-only (client credentials)
|
||||
/// credentials against the org tenant.
|
||||
///
|
||||
/// Registered as a singleton in Program.cs — one GraphServiceClient per process.
|
||||
/// </summary>
|
||||
public sealed class GraphService
|
||||
{
|
||||
private readonly GraphServiceClient _client;
|
||||
private readonly ILogger<GraphService> _log;
|
||||
|
||||
public GraphService(IConfiguration config, ILogger<GraphService> log)
|
||||
{
|
||||
_log = log;
|
||||
|
||||
var tenantId = config["Graph:TenantId"] ?? "";
|
||||
var clientId = config["Graph:ClientId"] ?? "";
|
||||
var clientSecret = config["Graph:ClientSecret"] ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tenantId) || string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
|
||||
{
|
||||
_log.LogWarning("[Graph] One or more Graph config values are missing (TenantId, ClientId, ClientSecret). " +
|
||||
"GET /api/admin/access/users will return an error until these are set.");
|
||||
}
|
||||
|
||||
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
|
||||
_client = new GraphServiceClient(credential, ["https://graph.microsoft.com/.default"]);
|
||||
}
|
||||
|
||||
public GraphServiceClient Client => _client;
|
||||
}
|
||||
150
Management/Services/RegistrationClient.cs
Normal file
150
Management/Services/RegistrationClient.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Management.Services;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client for calling the Registration Azure Function.
|
||||
///
|
||||
/// Configuration (appsettings.json):
|
||||
/// "Registration": {
|
||||
/// "BaseUrl": "https://your-function-app.azurewebsites.net/api",
|
||||
/// "FunctionKey": "your-function-key-here"
|
||||
/// }
|
||||
///
|
||||
/// Registered in DI as a typed HttpClient.
|
||||
/// </summary>
|
||||
public class RegistrationClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly ILogger<RegistrationClient> _log;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public RegistrationClient(HttpClient http, IConfiguration config, ILogger<RegistrationClient> log)
|
||||
{
|
||||
_http = http;
|
||||
_log = log;
|
||||
|
||||
var baseUrl = config["Registration:BaseUrl"];
|
||||
var functionKey = config["Registration:FunctionKey"];
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
_http.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/");
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
log.LogWarning(ex, "[RegistrationClient] Invalid BaseUrl: {BaseUrl}", baseUrl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogWarning("[RegistrationClient] Registration:BaseUrl not configured — registration proxy disabled");
|
||||
}
|
||||
|
||||
// Function key sent as query param (Azure Functions default auth)
|
||||
if (!string.IsNullOrWhiteSpace(functionKey))
|
||||
{
|
||||
// Store key for per-request query string injection
|
||||
_functionKey = functionKey;
|
||||
}
|
||||
|
||||
_log.LogInformation("[RegistrationClient] Configured. BaseUrl={BaseUrl} KeyPresent={HasKey}",
|
||||
_http.BaseAddress, !string.IsNullOrWhiteSpace(functionKey));
|
||||
}
|
||||
|
||||
private readonly string? _functionKey;
|
||||
|
||||
// ── API Methods ──
|
||||
|
||||
public async Task<JsonDocument?> GetPendingAsync(CancellationToken ct)
|
||||
{
|
||||
return await GetAsync("registration/pending", ct);
|
||||
}
|
||||
|
||||
public async Task<JsonDocument?> GetByIdAsync(string registrationId, CancellationToken ct)
|
||||
{
|
||||
return await GetAsync($"registration/item/{registrationId}", ct);
|
||||
}
|
||||
|
||||
public async Task<JsonDocument?> RejectAsync(string registrationId, string? reason, CancellationToken ct)
|
||||
{
|
||||
return await PostAsync($"registration/action/{registrationId}/reject", new { reason }, ct);
|
||||
}
|
||||
|
||||
public async Task<JsonDocument?> CompleteAsync(string registrationId, string? platformClientId, CancellationToken ct)
|
||||
{
|
||||
return await PostAsync($"registration/action/{registrationId}/complete", new { platformClientId }, ct);
|
||||
}
|
||||
|
||||
// ── Internal HTTP helpers ──
|
||||
|
||||
private async Task<JsonDocument?> GetAsync(string path, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = AppendKey(path);
|
||||
_log.LogInformation("[RegistrationClient] GET {Path}", path);
|
||||
|
||||
var response = await _http.GetAsync(url, ct);
|
||||
var body = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_log.LogWarning("[RegistrationClient] GET {Path} → {Status}: {Body}",
|
||||
path, (int)response.StatusCode, body[..Math.Min(200, body.Length)]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonDocument.Parse(body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "[RegistrationClient] GET {Path} failed", path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<JsonDocument?> PostAsync(string path, object? payload, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = AppendKey(path);
|
||||
var json = payload != null ? JsonSerializer.Serialize(payload, JsonOpts) : "{}";
|
||||
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
_log.LogInformation("[RegistrationClient] POST {Path}", path);
|
||||
|
||||
var response = await _http.PostAsync(url, content, ct);
|
||||
var body = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_log.LogWarning("[RegistrationClient] POST {Path} → {Status}: {Body}",
|
||||
path, (int)response.StatusCode, body[..Math.Min(200, body.Length)]);
|
||||
}
|
||||
|
||||
return JsonDocument.Parse(body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "[RegistrationClient] POST {Path} failed", path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string AppendKey(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_functionKey))
|
||||
return path;
|
||||
|
||||
var separator = path.Contains('?') ? '&' : '?';
|
||||
return $"{path}{separator}code={_functionKey}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user