fix(07): fix people picker selection and audit service authentication
People picker ListBox used MouseBinding which fires before SelectedItem updates, causing null CommandParameter. Replaced with SelectionChanged event handler in code-behind. AuditUsersAsync created TenantProfile with empty ClientId, causing ArgumentException in SessionManager. Added currentProfile parameter to pass the authenticated tenant's ClientId through. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,13 @@ public class UserAccessAuditServiceTests
|
||||
FolderDepth: 1,
|
||||
IncludeSubsites: false);
|
||||
|
||||
private static TenantProfile DefaultProfile => new()
|
||||
{
|
||||
Name = "Test",
|
||||
TenantUrl = "https://contoso.sharepoint.com",
|
||||
ClientId = "test-client-id"
|
||||
};
|
||||
|
||||
// ── Test 1: Filter by target user login ───────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
@@ -77,6 +84,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -103,6 +111,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -127,6 +136,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -151,6 +161,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -175,6 +186,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -199,6 +211,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -223,6 +236,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -248,6 +262,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { extLogin },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -272,6 +287,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@x.com", "bob@x.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -298,6 +314,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -324,6 +341,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
session.Object,
|
||||
DefaultProfile,
|
||||
Array.Empty<string>(),
|
||||
new[] { MakeSite() },
|
||||
DefaultOptions,
|
||||
@@ -367,6 +385,7 @@ public class UserAccessAuditServiceTests
|
||||
|
||||
var result = await svc.AuditUsersAsync(
|
||||
mockSession.Object,
|
||||
DefaultProfile,
|
||||
new[] { "alice@contoso.com" },
|
||||
sites,
|
||||
DefaultOptions,
|
||||
|
||||
@@ -49,6 +49,7 @@ public class UserAccessAuditViewModelTests
|
||||
mockAudit
|
||||
.Setup(s => s.AuditUsersAsync(
|
||||
It.IsAny<ISessionManager>(),
|
||||
It.IsAny<TenantProfile>(),
|
||||
It.IsAny<IReadOnlyList<string>>(),
|
||||
It.IsAny<IReadOnlyList<SiteInfo>>(),
|
||||
It.IsAny<ScanOptions>(),
|
||||
@@ -65,6 +66,14 @@ public class UserAccessAuditViewModelTests
|
||||
mockSession.Object,
|
||||
NullLogger<FeatureViewModelBase>.Instance);
|
||||
|
||||
// Set a default profile so RunOperationAsync doesn't early-return
|
||||
vm._currentProfile = new TenantProfile
|
||||
{
|
||||
Name = "Test",
|
||||
TenantUrl = "https://contoso.sharepoint.com",
|
||||
ClientId = "test-client-id"
|
||||
};
|
||||
|
||||
return (vm, mockAudit, mockGraph);
|
||||
}
|
||||
|
||||
@@ -83,6 +92,7 @@ public class UserAccessAuditViewModelTests
|
||||
auditMock.Verify(
|
||||
s => s.AuditUsersAsync(
|
||||
It.IsAny<ISessionManager>(),
|
||||
It.IsAny<TenantProfile>(),
|
||||
It.IsAny<IReadOnlyList<string>>(),
|
||||
It.IsAny<IReadOnlyList<SiteInfo>>(),
|
||||
It.IsAny<ScanOptions>(),
|
||||
|
||||
@@ -22,6 +22,7 @@ public interface IUserAccessAuditService
|
||||
/// <returns>Flat list of access entries for the target users.</returns>
|
||||
Task<IReadOnlyList<UserAccessEntry>> AuditUsersAsync(
|
||||
ISessionManager sessionManager,
|
||||
TenantProfile currentProfile,
|
||||
IReadOnlyList<string> targetUserLogins,
|
||||
IReadOnlyList<SiteInfo> sites,
|
||||
ScanOptions options,
|
||||
|
||||
@@ -24,6 +24,7 @@ public class UserAccessAuditService : IUserAccessAuditService
|
||||
|
||||
public async Task<IReadOnlyList<UserAccessEntry>> AuditUsersAsync(
|
||||
ISessionManager sessionManager,
|
||||
TenantProfile currentProfile,
|
||||
IReadOnlyList<string> targetUserLogins,
|
||||
IReadOnlyList<SiteInfo> sites,
|
||||
ScanOptions options,
|
||||
@@ -53,7 +54,7 @@ public class UserAccessAuditService : IUserAccessAuditService
|
||||
var profile = new TenantProfile
|
||||
{
|
||||
TenantUrl = site.Url,
|
||||
ClientId = string.Empty, // Will be set by SessionManager from cached session
|
||||
ClientId = currentProfile.ClientId,
|
||||
Name = site.Title
|
||||
};
|
||||
|
||||
|
||||
@@ -229,8 +229,15 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
FolderDepth: 1,
|
||||
IncludeSubsites: IncludeSubsites);
|
||||
|
||||
if (_currentProfile == null)
|
||||
{
|
||||
StatusMessage = "No tenant profile selected. Please connect first.";
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = await _auditService.AuditUsersAsync(
|
||||
_sessionManager,
|
||||
_currentProfile,
|
||||
userLogins,
|
||||
effectiveSites,
|
||||
scanOptions,
|
||||
|
||||
@@ -31,8 +31,10 @@
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<ListBox ItemsSource="{Binding SearchResults}" MaxHeight="120"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0,0,0,4">
|
||||
<ListBox x:Name="SearchResultsListBox"
|
||||
ItemsSource="{Binding SearchResults}" MaxHeight="120"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0,0,0,4"
|
||||
SelectionChanged="SearchResultsListBox_SelectionChanged">
|
||||
<ListBox.Style>
|
||||
<Style TargetType="ListBox">
|
||||
<Style.Triggers>
|
||||
@@ -54,11 +56,6 @@
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.InputBindings>
|
||||
<MouseBinding MouseAction="LeftClick"
|
||||
Command="{Binding AddUserCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=SelectedItem}" />
|
||||
</ListBox.InputBindings>
|
||||
</ListBox>
|
||||
<ItemsControl ItemsSource="{Binding SelectedUsers}" Margin="0,0,0,4">
|
||||
<ItemsControl.ItemTemplate>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Windows.Controls;
|
||||
using SharepointToolbox.Services;
|
||||
using SharepointToolbox.ViewModels.Tabs;
|
||||
|
||||
namespace SharepointToolbox.Views.Tabs;
|
||||
@@ -10,4 +11,17 @@ public partial class UserAccessAuditView : UserControl
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
private void SearchResultsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox && listBox.SelectedItem is GraphUserResult user)
|
||||
{
|
||||
var vm = (UserAccessAuditViewModel)DataContext;
|
||||
if (vm.AddUserCommand.CanExecute(user))
|
||||
vm.AddUserCommand.Execute(user);
|
||||
|
||||
// Clear selection so the same item can be re-selected if needed
|
||||
listBox.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user