feat: Implement Authentik group synchronization and add confirmation dialogs for service management

This commit is contained in:
Matt Batchelder
2026-03-04 21:33:29 -05:00
parent 56d48b6062
commit 9493bdb9df
19 changed files with 556 additions and 21 deletions

View File

@@ -460,6 +460,11 @@ public class PostInstanceInitService
samlConfig != null
? $" (Authentik provider={samlConfig.ProviderId})"
: " (without Authentik — needs manual IdP config)");
// ── 5. Sync Authentik groups to Xibo ──────────────────────────────
// Pre-create Authentik groups as Xibo user groups so they're available
// immediately (before any user logs in via SSO).
await SyncGroupsFromAuthentikAsync(abbrev, instanceUrl, settings, ct);
}
catch (Exception ex)
{
@@ -469,6 +474,89 @@ public class PostInstanceInitService
}
}
/// <summary>
/// Fetches all groups from Authentik and creates matching user groups in the
/// specified Xibo instance. Groups that already exist in Xibo are skipped.
/// This ensures that groups are available in Xibo for permission assignment
/// before any user logs in via SAML SSO.
/// </summary>
public async Task<int> SyncGroupsFromAuthentikAsync(
string abbrev,
string instanceUrl,
SettingsService settings,
CancellationToken ct = default)
{
var synced = 0;
try
{
_logger.LogInformation("[GroupSync] Syncing Authentik groups to Xibo for {Abbrev}", abbrev);
using var scope = _services.CreateScope();
var authentik = scope.ServiceProvider.GetRequiredService<IAuthentikService>();
var xibo = scope.ServiceProvider.GetRequiredService<XiboApiService>();
// ── 1. Fetch groups from Authentik ────────────────────────────────
var authentikGroups = await authentik.ListGroupsAsync(ct: ct);
if (authentikGroups.Count == 0)
{
_logger.LogInformation("[GroupSync] No groups found in Authentik — nothing to sync");
return 0;
}
_logger.LogInformation("[GroupSync] Found {Count} Authentik group(s) to sync", authentikGroups.Count);
// ── 2. Authenticate to Xibo ───────────────────────────────────────
var oauthClientId = await settings.GetAsync(SettingsService.InstanceOAuthClientId(abbrev));
var oauthSecretId = await settings.GetAsync(SettingsService.InstanceOAuthSecretId(abbrev));
if (string.IsNullOrWhiteSpace(oauthClientId) || string.IsNullOrWhiteSpace(oauthSecretId))
{
_logger.LogWarning("[GroupSync] No OAuth credentials for {Abbrev} — cannot sync groups", abbrev);
return 0;
}
var bws = scope.ServiceProvider.GetRequiredService<IBitwardenSecretService>();
var oauthSecret = await bws.GetSecretAsync(oauthSecretId);
var accessToken = await xibo.LoginAsync(instanceUrl, oauthClientId, oauthSecret.Value);
// ── 3. List existing Xibo groups ──────────────────────────────────
var existingGroups = await xibo.ListUserGroupsAsync(instanceUrl, accessToken);
var existingNames = new HashSet<string>(
existingGroups.Select(g => g.Group),
StringComparer.OrdinalIgnoreCase);
// ── 4. Create missing groups in Xibo ──────────────────────────────
foreach (var group in authentikGroups)
{
if (existingNames.Contains(group.Name))
{
_logger.LogDebug("[GroupSync] Group '{Name}' already exists in Xibo", group.Name);
continue;
}
try
{
var groupId = await xibo.CreateUserGroupAsync(instanceUrl, accessToken, group.Name);
_logger.LogInformation("[GroupSync] Created Xibo group '{Name}' (id={Id})", group.Name, groupId);
synced++;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[GroupSync] Failed to create Xibo group '{Name}'", group.Name);
}
}
_logger.LogInformation("[GroupSync] Sync complete for {Abbrev}: {Synced} group(s) created", abbrev, synced);
}
catch (Exception ex)
{
_logger.LogError(ex, "[GroupSync] Group sync failed for {Abbrev}: {Message}", abbrev, ex.Message);
// Don't rethrow — group sync failure should not block other operations
}
return synced;
}
// ─────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────