Files
AutopilotMonitor/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantConfiguration.cs
T

448 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace AutopilotMonitor.Shared.Models
{
/// <summary>
/// Tenant-specific configuration stored in Azure Table Storage
/// PartitionKey = TenantId
/// RowKey = "config"
/// </summary>
public class TenantConfiguration
{
/// <summary>
/// Tenant ID (PartitionKey in Table Storage)
/// </summary>
public string TenantId { get; set; }
/// <summary>
/// Domain name extracted from the first user's UPN
/// Used for display purposes (e.g., contoso.com)
/// </summary>
public string DomainName { get; set; }
/// <summary>
/// When the configuration was last updated
/// </summary>
public DateTime LastUpdated { get; set; }
/// <summary>
/// Updated by (user email or system)
/// </summary>
public string UpdatedBy { get; set; }
// ===== TENANT STATUS =====
/// <summary>
/// Whether this tenant is disabled/suspended
/// If true, users from this tenant cannot log in
/// Default: false
/// </summary>
public bool Disabled { get; set; } = false;
/// <summary>
/// Optional reason why the tenant was disabled
/// Displayed to users attempting to log in
/// </summary>
public string DisabledReason { get; set; }
/// <summary>
/// Optional date/time until which the tenant is disabled
/// If set and in the past, the tenant can be automatically re-enabled
/// If null, the tenant remains disabled until manually re-enabled
/// </summary>
public DateTime? DisabledUntil { get; set; }
// ===== SECURITY SETTINGS =====
/// <summary>
/// Rate limit: Maximum requests per minute per device
/// This value is synchronized from the global AdminConfiguration
/// Default: 100
/// </summary>
public int RateLimitRequestsPerMinute { get; set; } = 100;
/// <summary>
/// Optional custom rate limit for this tenant (overrides RateLimitRequestsPerMinute)
/// If set (not null), this custom value takes precedence over the global default
/// Note: This is only configurable by Galactic Admins directly in the database
/// </summary>
public int? CustomRateLimitRequestsPerMinute { get; set; } = null;
/// <summary>
/// Hardware whitelist: Allowed manufacturers (supports wildcards like "Dell*")
/// Comma-separated list
/// </summary>
public string ManufacturerWhitelist { get; set; } = "Dell*,HP*,Lenovo*,Microsoft Corporation";
/// <summary>
/// Hardware whitelist: Allowed models (supports wildcards like "Latitude*")
/// Comma-separated list
/// Default: "*" (all models allowed)
/// </summary>
public string ModelWhitelist { get; set; } = "*";
/// <summary>
/// Whether to validate devices against Intune Autopilot device registration
/// Requires Graph API integration (admin consent for DeviceManagementServiceConfig.Read.All)
/// </summary>
public bool ValidateAutopilotDevice { get; set; } = false;
/// <summary>
/// Whether to validate devices against Intune Corporate Device Identifiers
/// (manufacturer + model + serial number via importedDeviceIdentities/searchExistingIdentities).
/// Requires Graph API integration (admin consent for DeviceManagementServiceConfig.ReadWrite.All)
/// </summary>
public bool ValidateCorporateIdentifier { get; set; } = false;
/// <summary>
/// Emergency bypass for agent security gate (Galactic Admin use only).
/// If true, agent requests are accepted even when ValidateAutopilotDevice is false.
/// Default: false
/// </summary>
public bool AllowInsecureAgentRequests { get; set; } = false;
// ===== DATA MANAGEMENT SETTINGS =====
/// <summary>
/// Data retention period in days
/// Sessions and events older than this will be deleted by the daily maintenance job
/// Default: 90 days
/// </summary>
public int DataRetentionDays { get; set; } = 90;
/// <summary>
/// Session timeout in hours
/// Sessions in "InProgress" status longer than this will be marked as "Failed - Timed Out"
/// This prevents stalled sessions from running indefinitely and skewing statistics
/// Recommended: Use the same value as your ESP (Enrollment Status Page) timeout
/// Default: 5 hours
/// </summary>
public int SessionTimeoutHours { get; set; } = 5;
// ===== PAYLOAD SETTINGS =====
/// <summary>
/// Maximum decompressed NDJSON payload size in MB
/// Protects against memory exhaustion and denial-of-service attacks
/// Default: 5 MB
/// </summary>
public int MaxNdjsonPayloadSizeMB { get; set; } = 5;
// ===== AGENT COLLECTOR SETTINGS =====
/// <summary>
/// Enable Performance Collector (CPU, memory, disk, network monitoring)
/// Generates ~1 event per interval - can create significant traffic
/// Default: true
/// </summary>
public bool EnablePerformanceCollector { get; set; } = true;
/// <summary>
/// Performance collector interval in seconds
/// Default: 30 seconds
/// </summary>
public int PerformanceCollectorIntervalSeconds { get; set; } = 30;
/// <summary>
/// Seconds to wait for the Windows Hello wizard after ESP exit
/// Default: 30 seconds
/// </summary>
public int HelloWaitTimeoutSeconds { get; set; } = 30;
// ===== AGENT AUTH CIRCUIT BREAKER =====
/// <summary>
/// Maximum consecutive authentication failures (401/403) before the agent shuts down.
/// null = use default (5). 0 = disabled (retry forever).
/// </summary>
public int? MaxAuthFailures { get; set; }
/// <summary>
/// Maximum time in minutes the agent keeps retrying after the first auth failure.
/// null = use default (0 = disabled, only MaxAuthFailures applies).
/// </summary>
public int? AuthFailureTimeoutMinutes { get; set; }
/// <summary>
/// Maximum agent lifetime in minutes. Safety net to prevent zombie agents.
/// null = use default (360 = 6 hours). 0 = disabled (no lifetime limit).
/// </summary>
public int? AgentMaxLifetimeMinutes { get; set; }
// ===== AGENT BEHAVIOR OVERRIDES =====
/// <summary>
/// Whether to self-destruct after enrollment completion (remove Scheduled Task and all files).
/// null = use agent default (true).
/// </summary>
public bool? SelfDestructOnComplete { get; set; } = true;
/// <summary>
/// Preserve logs during self-destruct.
/// null = use agent default (false).
/// </summary>
public bool? KeepLogFile { get; set; } = false;
/// <summary>
/// Whether to reboot the device after enrollment completes.
/// null = use agent default (false).
/// </summary>
public bool? RebootOnComplete { get; set; }
/// <summary>
/// Delay in seconds before the reboot is initiated (shutdown.exe /r /t X).
/// null = use agent default (10 seconds).
/// </summary>
public int? RebootDelaySeconds { get; set; }
/// <summary>
/// Whether to enable geo-location detection (queries external IP service).
/// null = use agent default (true).
/// </summary>
public bool? EnableGeoLocation { get; set; }
/// <summary>
/// Whether to write a match log for every IME log line matched by a pattern.
/// When true, the agent writes to the default path Constants.ImeMatchLogPath.
/// null = use agent default (false).
/// </summary>
public bool? EnableImeMatchLog { get; set; }
/// <summary>
/// Log verbosity level override for this tenant's agents.
/// null = use agent default ("Info"). Values: "Info", "Debug", "Verbose".
/// </summary>
public string LogLevel { get; set; }
/// <summary>
/// Maximum events per upload batch.
/// null = use agent default (100).
/// </summary>
public int? MaxBatchSize { get; set; }
/// <summary>
/// Whether to show a visual enrollment summary dialog to the end user
/// after enrollment completes (success or failure).
/// null = use agent default (false).
/// </summary>
public bool? ShowEnrollmentSummary { get; set; }
/// <summary>
/// Auto-close timeout in seconds for the enrollment summary dialog.
/// null = use agent default (60). 0 = no auto-close.
/// </summary>
public int? EnrollmentSummaryTimeoutSeconds { get; set; }
/// <summary>
/// Optional URL to a branding image displayed as a banner at the top of the enrollment summary dialog.
/// Expected size: 540 x 80 px. Larger images will be center-cropped.
/// </summary>
public string EnrollmentSummaryBrandingImageUrl { get; set; }
/// <summary>
/// Maximum time in seconds the agent retries launching the enrollment summary dialog
/// when the user's desktop is locked by a credential UI (e.g. Windows Hello).
/// null = use agent default (120). 0 = no retry (single attempt).
/// </summary>
public int? EnrollmentSummaryLaunchRetrySeconds { get; set; }
/// <summary>
/// Whether to show PowerShell script stdout in the web UI.
/// When false, only stderr (error output) is visible for troubleshooting.
/// stdout may contain sensitive data (credentials, tokens).
/// Default true (show stdout). Data is always collected regardless of this setting.
/// </summary>
public bool? ShowScriptOutput { get; set; } = true;
// ===== AGENT ANALYZER SETTINGS =====
/// <summary>
/// Whether the LocalAdminAnalyzer is enabled for this tenant's devices.
/// null = use agent default (true).
/// </summary>
public bool? EnableLocalAdminAnalyzer { get; set; } = null;
/// <summary>
/// JSON-serialized list of additional local account names that are considered expected
/// on a newly enrolled device (merged with built-in defaults on the agent).
/// Example: ["SupportAdmin", "TechDesk"]
/// </summary>
public string LocalAdminAllowedAccountsJson { get; set; }
/// <summary>
/// Returns the deserialized list of additional allowed local admin account names.
/// </summary>
public List<string> GetLocalAdminAllowedAccounts()
{
if (string.IsNullOrEmpty(LocalAdminAllowedAccountsJson))
return new List<string>();
try
{
return JsonSerializer.Deserialize<List<string>>(LocalAdminAllowedAccountsJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
?? new List<string>();
}
catch
{
return new List<string>();
}
}
// ===== BOOTSTRAP TOKEN =====
/// <summary>
/// Whether OOBE Bootstrap Sessions are enabled for this tenant.
/// When false (default), the Bootstrap Sessions feature is hidden in the UI
/// and all bootstrap API endpoints reject requests for this tenant.
/// Only configurable by Galactic Admins.
/// </summary>
public bool BootstrapTokenEnabled { get; set; } = false;
// ===== DIAGNOSTICS LOG PATHS =====
/// <summary>
/// JSON-serialized list of tenant-specific additional log paths/wildcards
/// to include in the diagnostics ZIP package (additive to global paths).
/// Each entry: { "path": "...", "description": "...", "isBuiltIn": false }
/// </summary>
public string DiagnosticsLogPathsJson { get; set; }
/// <summary>
/// Returns the deserialized list of tenant-specific diagnostics log paths.
/// </summary>
public List<DiagnosticsLogPath> GetDiagnosticsLogPaths()
{
if (string.IsNullOrEmpty(DiagnosticsLogPathsJson))
return new List<DiagnosticsLogPath>();
try
{
return JsonSerializer.Deserialize<List<DiagnosticsLogPath>>(DiagnosticsLogPathsJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
?? new List<DiagnosticsLogPath>();
}
catch
{
return new List<DiagnosticsLogPath>();
}
}
// ===== DIAGNOSTICS SETTINGS =====
/// <summary>
/// Azure Blob Storage Container SAS URL for diagnostics package upload.
/// Each tenant provides their own container — data stays in the customer's storage.
/// If null or empty, diagnostics upload is disabled.
/// </summary>
public string DiagnosticsBlobSasUrl { get; set; }
/// <summary>
/// When to upload diagnostics packages: "Off", "Always", "OnFailure".
/// Default: "Off"
/// </summary>
public string DiagnosticsUploadMode { get; set; } = "Off";
// ===== TRACE EVENTS =====
/// <summary>
/// Whether the agent should send Trace-severity events to the backend.
/// Trace events capture key agent decisions for backend-side troubleshooting.
/// Default: true (on in preview). Can be disabled per tenant to reduce traffic.
/// </summary>
public bool SendTraceEvents { get; set; } = true;
// ===== TEAMS NOTIFICATIONS =====
/// <summary>
/// URL of the Teams Incoming Webhook for enrollment notifications.
/// If null or empty, no notifications are sent.
/// </summary>
public string TeamsWebhookUrl { get; set; }
/// <summary>
/// Send a Teams notification when an enrollment completes successfully.
/// Default: true
/// </summary>
public bool TeamsNotifyOnSuccess { get; set; } = true;
/// <summary>
/// Send a Teams notification when an enrollment fails.
/// Default: true
/// </summary>
public bool TeamsNotifyOnFailure { get; set; } = true;
// ===== HELPER METHODS =====
/// <summary>
/// Checks if the tenant is currently disabled
/// Takes into account DisabledUntil if set
/// </summary>
public bool IsCurrentlyDisabled()
{
if (!Disabled)
return false;
// If DisabledUntil is set and in the past, tenant is no longer disabled
if (DisabledUntil.HasValue && DisabledUntil.Value <= DateTime.UtcNow)
return false;
return true;
}
/// <summary>
/// Gets manufacturer whitelist as array
/// </summary>
public string[] GetManufacturerWhitelist()
{
if (string.IsNullOrEmpty(ManufacturerWhitelist))
return new[] { "*" };
return ManufacturerWhitelist.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// Gets model whitelist as array
/// </summary>
public string[] GetModelWhitelist()
{
if (string.IsNullOrEmpty(ModelWhitelist))
return new[] { "*" };
return ModelWhitelist.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// Creates default configuration for a tenant
/// </summary>
public static TenantConfiguration CreateDefault(string tenantId)
{
return new TenantConfiguration
{
TenantId = tenantId,
DomainName = "",
LastUpdated = DateTime.UtcNow,
UpdatedBy = "System",
Disabled = false,
DisabledReason = null,
DisabledUntil = null,
RateLimitRequestsPerMinute = 100,
CustomRateLimitRequestsPerMinute = null,
ManufacturerWhitelist = "Dell*,HP*,Lenovo*,Microsoft Corporation",
ModelWhitelist = "*",
ValidateAutopilotDevice = false,
AllowInsecureAgentRequests = false,
DataRetentionDays = 90,
SessionTimeoutHours = 5,
MaxNdjsonPayloadSizeMB = 5,
EnablePerformanceCollector = true,
PerformanceCollectorIntervalSeconds = 30,
SelfDestructOnComplete = true,
KeepLogFile = false,
ShowScriptOutput = true
};
}
}
}