Initial commit

This commit is contained in:
2026-06-02 10:51:14 +02:00
committed by kawa
commit d19092c84e
182 changed files with 13757 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.SharePoint.Client;
using Serilog;
using SharepointToolbox.Web.Core.Helpers;
using SharepointToolbox.Web.Core.Models;
using SharepointToolbox.Web.Services.Audit;
using AppGraphClientFactory = SharepointToolbox.Web.Infrastructure.Auth.GraphClientFactory;
namespace SharepointToolbox.Web.Services;
public class BulkMemberService : IBulkMemberService
{
private readonly AppGraphClientFactory _graphClientFactory;
private readonly IAuditService _audit;
public BulkMemberService(AppGraphClientFactory graphClientFactory, IAuditService audit)
{
_graphClientFactory = graphClientFactory;
_audit = audit;
}
public async Task<BulkOperationSummary<BulkMemberRow>> AddMembersAsync(
ClientContext ctx, TenantProfile profile,
IReadOnlyList<BulkMemberRow> rows,
IProgress<OperationProgress> progress, CancellationToken ct)
{
var result = await BulkOperationRunner.RunAsync(rows,
async (row, idx, token) => await AddSingleMemberAsync(ctx, profile, row, progress, token),
progress, ct);
var sites = rows.Select(r => r.GroupUrl ?? ctx.Url).Distinct().ToList();
await _audit.LogAsync("BulkAddMembers", profile.Name, sites, $"{result.SuccessCount} succeeded, {(result.TotalCount - result.SuccessCount)} failed");
return result;
}
private async Task AddSingleMemberAsync(
ClientContext ctx, TenantProfile profile, BulkMemberRow row,
IProgress<OperationProgress> progress, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(row.GroupUrl))
{
await AddToClassicGroupAsync(ctx, row.GroupName, row.Email, row.Role, progress, ct);
return;
}
try
{
var graphClient = await _graphClientFactory.CreateClientAsync(profile);
var groupId = await ResolveGroupIdAsync(graphClient, row.GroupUrl, ct);
if (groupId != null)
{
await AddViaGraphAsync(graphClient, groupId, row.Email, row.Role, ct);
Log.Information("Added {Email} to M365 group {Group} via Graph", row.Email, row.GroupName);
return;
}
}
catch (OperationCanceledException) { throw; }
catch (Exception ex) { Log.Warning("Graph API failed for {Url}, falling back to CSOM: {Error}", row.GroupUrl, ex.Message); }
await AddToClassicGroupAsync(ctx, row.GroupName, row.Email, row.Role, progress, ct);
}
private static async Task AddViaGraphAsync(GraphServiceClient graphClient, string groupId, string email, string role, CancellationToken ct)
{
var user = await graphClient.Users[email].GetAsync(cancellationToken: ct);
if (user?.Id == null) throw new InvalidOperationException($"User not found: {email}");
var userRef = $"https://graph.microsoft.com/v1.0/directoryObjects/{user.Id}";
var body = new ReferenceCreate { OdataId = userRef };
if (role.Equals("Owner", StringComparison.OrdinalIgnoreCase))
await graphClient.Groups[groupId].Owners.Ref.PostAsync(body, cancellationToken: ct);
else
await graphClient.Groups[groupId].Members.Ref.PostAsync(body, cancellationToken: ct);
}
private static async Task<string?> ResolveGroupIdAsync(GraphServiceClient graphClient, string siteUrl, CancellationToken ct)
{
try
{
var uri = new Uri(siteUrl);
var site = await graphClient.Sites[$"{uri.Host}:{uri.AbsolutePath.TrimEnd('/')}"].GetAsync(cancellationToken: ct);
if (site?.Id == null) return null;
var groups = await graphClient.Groups.GetAsync(r =>
{
r.QueryParameters.Filter = "resourceProvisioningOptions/any(x:x eq 'Team')";
r.QueryParameters.Select = new[] { "id" };
}, cancellationToken: ct);
return groups?.Value?.FirstOrDefault()?.Id;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex) { Log.Debug("Group resolve failed for {Url}: {Error}", siteUrl, ex.Message); return null; }
}
private static async Task AddToClassicGroupAsync(
ClientContext ctx, string groupName, string email, string role,
IProgress<OperationProgress> progress, CancellationToken ct)
{
ctx.Load(ctx.Web.SiteGroups);
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
Microsoft.SharePoint.Client.Group? targetGroup = null;
foreach (var group in ctx.Web.SiteGroups)
if (group.Title.Equals(groupName, StringComparison.OrdinalIgnoreCase)) { targetGroup = group; break; }
if (targetGroup == null) throw new InvalidOperationException($"SharePoint group not found: {groupName}");
var user = ctx.Web.EnsureUser(email);
ctx.Load(user);
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
targetGroup.Users.AddUser(user);
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
}
}