using System; using System.Collections.Generic; using System.Threading.Tasks; namespace AutopilotMonitor.Shared.DataAccess { /// /// Repository for admin and tenant member management. /// Covers: GlobalAdmins, TenantAdmins, McpUsers, DelegatedAdmins tables. /// public interface IAdminRepository { // --- Global Admins --- Task IsGlobalAdminAsync(string upn); /// /// Resolves the platform role for a UPN: "GlobalAdmin", "GlobalReader", or null when there is no /// enabled GlobalAdmins row. Empty/missing Role on an existing enabled row ⇒ "GlobalAdmin" (back-compat). /// Task GetGlobalRoleAsync(string upn); Task> GetAllGlobalAdminsAsync(); Task AddGlobalAdminAsync(string upn, string addedBy); Task RemoveGlobalAdminAsync(string upn); Task DisableGlobalAdminAsync(string upn); // --- MCP Users --- Task IsMcpUserAsync(string upn); Task GetMcpUserAsync(string upn); Task> GetAllMcpUsersAsync(); Task AddMcpUserAsync(string upn, string addedBy); Task RemoveMcpUserAsync(string upn); Task SetMcpUserEnabledAsync(string upn, bool isEnabled); Task SetMcpUserUsagePlanAsync(string upn, string? usagePlan); // --- Delegated Admins (scoped-global / "MSP mode") --- /// Every assignment row across all delegated admins (full-table scan — the management UI lists /// all grants; the table holds only admin assignments, so this stays small). Not a hot path. Task> GetAllDelegatedAdminsAsync(); /// All assignment rows for one delegated-admin UPN (PK scan), regardless of status/enabled. /// Callers that need only effective scope filter on Status==Active && IsEnabled. Task> GetDelegatedTenantsAsync(string upn); /// All assignment rows targeting one tenant (cross-partition RowKey scan — admin UI, not hot path). /// Powers the customer's "who manages my tenant?" view including pending requests. Task> GetDelegatedAssigneesAsync(string tenantId); Task GetDelegatedAdminAsync(string upn, string tenantId); /// Creates or replaces an assignment row (upsert on PK=upn, RK=tenantId). Task UpsertDelegatedAdminAsync(string upn, string tenantId, string role, string status, string source, string grantedBy); Task SetDelegatedAdminStatusAsync(string upn, string tenantId, string status); Task SetDelegatedAdminEnabledAsync(string upn, string tenantId, bool isEnabled); Task RemoveDelegatedAdminAsync(string upn, string tenantId); // --- Tenant Groups (app-internal tenant bundles for delegated admins / "MSP mode") --- /// Creates a group (meta row) and returns the generated groupId. Task CreateTenantGroupAsync(string name, string createdBy); /// /// Updates a group's meta row: and are each left /// unchanged when null; an empty label clears it. Returns false if the group does not exist. /// Task UpdateTenantGroupAsync(string groupId, string? name, string? customerLabel); /// Deletes a group: all rows in its partition (meta + membership) AND every UPN /// assignment referencing it (cross-partition RowKey scan of the assignments table). Task DeleteTenantGroupAsync(string groupId); /// All groups with their tenant members + assignee counts — for the management UI (not hot path). Task> GetAllTenantGroupsAsync(); Task GetTenantGroupAsync(string groupId); /// Creates the meta row of a tenant-OWNED (self-service) group if missing — idempotent, never touches an existing group. Task EnsureOwnedTenantGroupAsync(string groupId, string name, string ownerTenantId); /// Ids of every group partition holding a membership row for the tenant (cross-partition RowKey scan; existence is re-checked meta-backed by callers). Task> GetGroupIdsContainingTenantAsync(string tenantId); Task AddTenantToGroupAsync(string groupId, string tenantId); Task RemoveTenantFromGroupAsync(string groupId, string tenantId); /// Tenant IDs (lowercase) in a group, excluding the meta row (HOT PATH — PartitionKey scan). Task> GetGroupTenantsAsync(string groupId); /// The group's tenant members PLUS its meta-row flags from the same single partition read /// (HOT PATH — scope resolution). Null when the group does not exist (no meta row). Task GetGroupMembershipAsync(string groupId); /// Creates or replaces a UPN→group assignment. Task AssignGroupAsync(string upn, string groupId, string role, bool isEnabled, string assignedBy); Task UnassignGroupAsync(string upn, string groupId); /// All group assignments for one UPN (HOT PATH — PartitionKey point-scan; scope resolution). Task> GetGroupAssignmentsForUpnAsync(string upn); /// All UPNs assigned to one group (cross-partition RowKey scan — management UI, not hot path). Task> GetGroupAssigneesAsync(string groupId); // --- Admin identity bindings (tid + oid behind every cross-tenant-role UPN) --- /// The binding for a UPN, or null when none exists (⇒ no cross-tenant role resolves). Task GetIdentityBindingAsync(string upn); /// Every binding — for the operator management UI (small table, not a hot path). Task> GetAllIdentityBindingsAsync(); /// Every binding homed in one tenant (single-partition property filter) — the delegated slot count's attribution. Task> GetIdentityBindingsByHomeTenantAsync(string homeTenantId); /// Creates or replaces the binding for a UPN. null/empty ⇒ pinned on first sign-in. Task UpsertIdentityBindingAsync(string upn, string tenantId, string? objectId, string boundBy); /// /// Pins onto an existing binding ONLY IF the binding is homed in /// and has no object id yet (optimistic-concurrency guarded). Returns the /// binding as stored after the attempt (null when no binding exists) so the caller can re-verify. /// Task TryPinIdentityObjectIdAsync(string upn, string tenantId, string objectId); Task RemoveIdentityBindingAsync(string upn); // --- Tenant Members --- Task> GetTenantMembersAsync(string tenantId); Task AddTenantMemberAsync(string tenantId, string upn, string addedBy, string role, bool canManageBootstrapTokens = false); Task RemoveTenantMemberAsync(string tenantId, string upn); Task UpdateMemberPermissionsAsync(string tenantId, string upn, string role, bool canManageBootstrapTokens); Task SetTenantMemberEnabledAsync(string tenantId, string upn, bool isEnabled); Task GetTenantMemberAsync(string tenantId, string upn); Task IsTenantAdminAsync(string tenantId, string upn); } public class GlobalAdminEntry { public string Upn { get; set; } = string.Empty; public bool IsEnabled { get; set; } = true; public DateTime AddedAt { get; set; } public string AddedBy { get; set; } = string.Empty; /// Platform role: "GlobalAdmin" (default) or "GlobalReader". Empty ⇒ GlobalAdmin (back-compat). public string Role { get; set; } = string.Empty; } /// /// One delegated-admin assignment: UPN X may access tenant Y at role Role. The "scoped global" tier /// (subset of tenants) between a single-tenant member and a platform GlobalAdmin. Surfaced as "MSP mode". /// public class DelegatedAdminEntry { public string Upn { get; set; } = string.Empty; public string TenantId { get; set; } = string.Empty; /// Constants.DelegatedRoles: "DelegatedReader" (default) or "DelegatedAdmin". public string Role { get; set; } = string.Empty; public bool IsEnabled { get; set; } = true; /// Constants.DelegatedStatus: "Active" / "PendingApproval" / "Revoked". Only Active confers scope. public string Status { get; set; } = string.Empty; /// Constants.DelegatedSource: "OperatorGranted" / "CustomerDelegated". public string Source { get; set; } = string.Empty; public DateTime GrantedAt { get; set; } public string GrantedBy { get; set; } = string.Empty; } /// /// A Tenant Group: an app-internal named bundle of tenants. A delegated admin assigned to the /// group (see ) gains read scope to every tenant in /// . Adding a tenant to the group grants it to all assignees at once. /// public class TenantGroup { public string GroupId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string CreatedBy { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } /// Tenant IDs (lowercase) in this group. public List TenantIds { get; set; } = new(); /// Number of UPNs assigned to this group (== .Count). public int AssigneeCount { get; set; } /// The UPNs assigned to this group (for the management UI). public List Assignees { get; set; } = new(); /// The managing tenant that owns this self-service group (msp-{tenantId}); null for operator-created groups. public string? OwnerTenantId { get; set; } /// Operator-created groups only: the name a managed customer sees for this access instead of the neutral "Platform support"; absent when none is set. The group name itself never reaches a customer. public string? CustomerLabel { get; set; } } /// /// Hot-path projection of one Tenant Group: its tenant members and the meta-row flags that shape the /// scope they confer, read from the single group partition (see ). /// public class TenantGroupMembership { public string GroupId { get; set; } = string.Empty; /// Tenant IDs (lowercase) in this group. public List TenantIds { get; set; } = new(); } /// /// The immutable Entra identity behind a cross-tenant-role UPN: the HOME tenant the UPN was granted for /// and, once known, the user's object id. A role row (GlobalAdmins / DelegatedAdmins / TenantGroupAssignments) /// confers nothing unless the caller's validated JWT matches this binding. /// public class AdminIdentityBinding { public string Upn { get; set; } = string.Empty; /// The admin's home Entra tenant id (lowercase GUID) — must equal the JWT tid. public string TenantId { get; set; } = string.Empty; /// The admin's Entra object id (lowercase GUID) — must equal the JWT oid. Empty ⇒ pinned on first sign-in. public string ObjectId { get; set; } = string.Empty; public string BoundBy { get; set; } = string.Empty; public DateTime BoundAt { get; set; } /// When the object id was pinned (grant time, or the first matching sign-in). Null while unpinned. public DateTime? ObjectIdPinnedAt { get; set; } public bool IsObjectIdPinned => !string.IsNullOrEmpty(ObjectId); } /// One UPN→group assignment. PK=UPN, RK=groupId in storage. public class TenantGroupAssignment { public string Upn { get; set; } = string.Empty; public string GroupId { get; set; } = string.Empty; /// Constants.DelegatedRoles: "DelegatedReader" (default) or "DelegatedAdmin". public string Role { get; set; } = string.Empty; public bool IsEnabled { get; set; } = true; public string AssignedBy { get; set; } = string.Empty; public DateTime AssignedAt { get; set; } } public class McpUserEntry { public string Upn { get; set; } = string.Empty; public bool IsEnabled { get; set; } = true; public DateTime AddedAt { get; set; } public string AddedBy { get; set; } = string.Empty; public string? UsagePlan { get; set; } } public class TenantMember { public string Upn { get; set; } = string.Empty; public string TenantId { get; set; } = string.Empty; public string Role { get; set; } = "Admin"; public bool IsEnabled { get; set; } = true; public bool CanManageBootstrapTokens { get; set; } public DateTime AddedAt { get; set; } public string AddedBy { get; set; } = string.Empty; } }