mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-25 05:00:07 +00:00
5163d7d6a0
73 sites in 68 files built the query collection with HttpUtility.ParseQueryString(req.Url.Query), 15 files used req.Query and five mixed both. HttpRequestData.Query is that same call, cached per request, and the ASP.NET Core request type does not override it, so the change is behaviour-neutral. - QueryParamGuardTests: ParseQueryString in the Functions project is limited to a named baseline of three sites that parse something other than the request query. - A fact pins req.Query against HttpUtility.ParseQueryString (repeated keys, encoded values, caching) so a worker update that changes the implementation fails a test. - Test mocks that set Url answer Query through CallBase; a loose mock returns null. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
67 lines
2.6 KiB
C#
67 lines
2.6 KiB
C#
using AutopilotMonitor.Functions.Helpers;
|
|
using AutopilotMonitor.Shared.DataAccess;
|
|
using AutopilotMonitor.Shared.Models;
|
|
using Microsoft.Azure.Functions.Worker;
|
|
using Microsoft.Azure.Functions.Worker.Http;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AutopilotMonitor.Functions.Functions.Admin
|
|
{
|
|
/// <summary>
|
|
/// Global Admin (or Global Reader) endpoint: lists web users currently active across all tenants,
|
|
/// i.e. those who made an authenticated request within the requested window (default 5 min).
|
|
/// Backed by the self-maintaining UserPresence table (see <c>UserPresenceMiddleware</c>).
|
|
/// Authentication + GlobalReadOrAdmin authorization enforced by PolicyEnforcementMiddleware.
|
|
/// </summary>
|
|
public class GetActiveUsersFunction
|
|
{
|
|
private const int DefaultWindowMinutes = 5;
|
|
private const int MinWindowMinutes = 1;
|
|
private const int MaxWindowMinutes = 60;
|
|
|
|
private readonly ILogger<GetActiveUsersFunction> _logger;
|
|
private readonly IMetricsRepository _metricsRepo;
|
|
|
|
public GetActiveUsersFunction(ILogger<GetActiveUsersFunction> logger, IMetricsRepository metricsRepo)
|
|
{
|
|
_logger = logger;
|
|
_metricsRepo = metricsRepo;
|
|
}
|
|
|
|
[Function("GetActiveUsers")]
|
|
public async Task<HttpResponseData> Run(
|
|
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "global/presence")] HttpRequestData req)
|
|
{
|
|
try
|
|
{
|
|
var query = req.Query;
|
|
var windowMinutes = QueryParams.Int(query["windowMinutes"], DefaultWindowMinutes, MinWindowMinutes, MaxWindowMinutes);
|
|
|
|
var now = DateTime.UtcNow;
|
|
var active = await _metricsRepo.GetActivePresenceAsync(TimeSpan.FromMinutes(windowMinutes));
|
|
|
|
var users = active.Select(u => new ActiveUserItem
|
|
{
|
|
TenantId = u.TenantId,
|
|
Upn = u.Upn,
|
|
UserRole = u.UserRole,
|
|
LastSeen = u.LastSeen,
|
|
SecondsAgo = (int)Math.Max(0, (now - u.LastSeen).TotalSeconds)
|
|
}).ToList();
|
|
|
|
return await req.OkAsync(new GetActiveUsersResponse
|
|
{
|
|
Success = true,
|
|
WindowMinutes = windowMinutes,
|
|
ActiveCount = users.Count,
|
|
Users = users
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return await req.InternalServerErrorAsync(_logger, ex, "Get active users");
|
|
}
|
|
}
|
|
}
|
|
}
|