Files
AutopilotMonitor/src/Shared/AutopilotMonitor.Shared/Models/Config/TenantFeatureFlagsApiModels.cs
T
Oliver Kieselbach 2e736f40ad feat(backend,shared): Intune Enrollment Validation admits enrolled devices without pre-registration (D-282)
The certificate's Intune managedDevice id is looked up in the tenant's Intune. With the tenant
switch ValidateIntuneDeviceBinding on, that lookup is the last accepting link of the validator
chain: an enrolled device that is not being retired, wiped or deleted is admitted as
ValidatorType.IntuneEnrollment. A 404 for a certificate younger than 60 minutes is
NotFoundRecentEnrollment (503, never cached); a missing grant is 503 with 120 s.

For devices another validator admitted, the lookup only observes: in the background, never
admitting or rejecting, and only when the app token carries
DeviceManagementManagedDevices.Read.All, so a tenant without the add-on costs no Graph call.
Outcomes ride on the request row (CertDeviceBinding, role, serial match, owner and enrollment
type).

TenantConfiguration.HasAnyDeviceValidation() is the single gate precondition; the feature flags
expose it as deviceValidationEnabled (replaces validateAutopilotDevice).

The agent parses ValidatorType strictly: release the agent before this backend.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 11:57:26 +02:00

55 lines
3.4 KiB
C#

using System;
namespace AutopilotMonitor.Shared.Models
{
// Declaration order == wire order.
/// <summary>
/// Response of <c>GET config/{tenantId}/feature-flags</c>: the member-readable subset of
/// the tenant configuration — UI display toggles, feature switches and the read-time
/// edition/entitlement surface. Adding a field here is a deliberate decision that the
/// field is non-sensitive (no webhook URLs, SAS tokens, allowlists, addresses).
/// </summary>
public class TenantFeatureFlagsResponse : IApiResponse
{
/// <summary>EFFECTIVE bootstrap availability (Pro includes it; the GA flag is the additive Community enable) — field name kept for web compatibility.</summary>
public bool BootstrapTokenEnabled { get; set; }
/// <summary>Whether an on-demand diagnostics upload can succeed right now (mode not Off + usable destination). Deliberately no destination detail.</summary>
public bool DiagnosticsUploadConfigured { get; set; }
/// <summary>At least one device-validation method is enabled (<see cref="TenantConfiguration.HasAnyDeviceValidation"/>); false drives the "agent ingestion is blocked" dashboard banner.</summary>
public bool DeviceValidationEnabled { get; set; }
/// <summary>Dual app-reg self-service migration: consent flow targets the NEW app registration. Non-sensitive — exposes no client ids.</summary>
public bool AppHomingFunnelActive { get; set; }
public bool ShowScriptOutput { get; set; }
public bool EnableSoftwareInventoryAnalyzer { get; set; }
public bool EnableIntegrityBypassAnalyzer { get; set; }
/// <summary>EFFECTIVE gather-rule unrestricted mode (Pro edition + GA gate + tenant toggle).</summary>
public bool UnrestrictedMode { get; set; }
/// <summary>Resolved edition, lowercase ("community" / "pro").</summary>
public string Edition { get; set; } = string.Empty;
/// <summary>Why: "community" | "plan" (own Pro tier) | "trial" | "msp" (Pro conferred by the managing tenant — shown as "Pro (MSP)").</summary>
public string EditionSource { get; set; } = string.Empty;
public bool IsTrial { get; set; }
/// <summary>Absent unless the tenant is on an active trial.</summary>
public DateTime? TrialExpiresUtc { get; set; }
public bool TrialAvailable { get; set; }
/// <summary>Whether a contact address is stored (boolean only — the address stays admin-gated).</summary>
public bool ContactEmailSet { get; set; }
/// <summary>Whether a company name is stored (boolean only — the value stays admin-gated).</summary>
public bool CompanyNameSet { get; set; }
public TenantFeatureEntitlements Entitlements { get; set; } = default!;
}
/// <summary>Read-time entitlement surface of the resolved edition.</summary>
public class TenantFeatureEntitlements
{
public int RetentionCapDays { get; set; }
/// <summary>Absent when the platform default applies.</summary>
public int? UserRateLimitPerMinute { get; set; }
public bool DelegatedAdminAllowed { get; set; }
public string McpUsagePlan { get; set; } = string.Empty;
/// <summary>Effective delegated (MSP) tenant slot limit (override or plan entitlement); 0 = no delegation.</summary>
public int MaxDelegatedTenants { get; set; }
}
}