1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

[PM-11123] Service layer for Notification Center (#4741)

* PM-11123: Service layer

* PM-11123: Service layer for Notification Center

* PM-11123: Throw error on unsupported requirement

* PM-11123: Missing await

* PM-11123: Cleanup

* PM-11123: Unit Test coverage

* PM-11123: Flipping the authorization logic to be exact match of fail, formatting

* PM-11123: Async warning

* PM-11123: Using AuthorizeOrThrowAsync, removal of redundant set new id

* PM-11123: UT typo

* PM-11123: UT fix
This commit is contained in:
Maciej Zieniuk
2024-10-02 19:23:19 +02:00
committed by GitHub
parent 9cb99298fc
commit f3f81deb98
29 changed files with 1918 additions and 0 deletions

View File

@ -0,0 +1,419 @@
#nullable enable
using Bit.Core.Context;
using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.NotificationCenter.Authorization;
using System.Security.Claims;
using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Entities;
using Microsoft.AspNetCore.Authorization;
using NSubstitute;
using Xunit;
[SutProviderCustomize]
[NotificationCustomize]
public class NotificationAuthorizationHandlerTests
{
private static void SetupUserPermission(SutProvider<NotificationAuthorizationHandler> sutProvider,
Guid? userId = null, Guid? organizationId = null, bool canAccessReports = false)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organizationId.GetValueOrDefault(Guid.NewGuid()))
.Returns(new CurrentContextOrganization());
sutProvider.GetDependency<ICurrentContext>().AccessReports(organizationId.GetValueOrDefault(Guid.NewGuid()))
.Returns(canAccessReports);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_UnsupportedNotificationOperationRequirement_Throws(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = new NotificationOperationsRequirement("UnsupportedOperation");
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(context));
}
[Theory]
[BitAutoData(nameof(NotificationOperations.Read))]
[BitAutoData(nameof(NotificationOperations.Create))]
[BitAutoData(nameof(NotificationOperations.Update))]
public async Task HandleAsync_NotLoggedIn_Unauthorized(
string requirementName,
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, userId: null);
var requirement = new NotificationOperationsRequirement(requirementName);
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData(nameof(NotificationOperations.Read))]
[BitAutoData(nameof(NotificationOperations.Create))]
[BitAutoData(nameof(NotificationOperations.Update))]
public async Task HandleAsync_ResourceEmpty_Unauthorized(
string requirementName,
SutProvider<NotificationAuthorizationHandler> sutProvider,
ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = new NotificationOperationsRequirement(requirementName);
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, null);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: true)]
public async Task HandleAsync_ReadRequirementGlobalNotification_Authorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = NotificationOperations.Read;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(global: false)]
public async Task HandleAsync_ReadRequirementUserNotMatching_Unauthorized(
bool hasOrganizationId,
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid(), notification.OrganizationId);
if (!hasOrganizationId)
{
notification.OrganizationId = null;
}
var requirement = NotificationOperations.Read;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(global: false)]
public async Task HandleAsync_ReadRequirementOrganizationNotMatching_Unauthorized(
bool hasUserId,
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, Guid.NewGuid());
if (!hasUserId)
{
notification.UserId = null;
}
var requirement = NotificationOperations.Read;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(true, true)]
[NotificationCustomize(global: false)]
public async Task HandleAsync_ReadRequirement_Authorized(
bool hasUserId,
bool hasOrganizationId,
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, notification.OrganizationId);
if (!hasUserId)
{
notification.UserId = null;
}
if (!hasOrganizationId)
{
notification.OrganizationId = null;
}
var requirement = NotificationOperations.Read;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: true)]
public async Task HandleAsync_CreateRequirementGlobalNotification_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = NotificationOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_CreateRequirementUserNotMatching_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid(), notification.OrganizationId);
notification.OrganizationId = null;
var requirement = NotificationOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_CreateRequirementOrganizationNotMatching_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, Guid.NewGuid());
var requirement = NotificationOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_CreateRequirementOrganizationUserNoAccessReportsPermission_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, notification.OrganizationId, canAccessReports: false);
var requirement = NotificationOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_CreateRequirementUserNotPartOfOrganization_Authorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId);
notification.OrganizationId = null;
var requirement = NotificationOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(global: false)]
public async Task HandleAsync_CreateRequirementOrganizationUserCanAccessReports_Authorized(
bool hasUserId,
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, notification.OrganizationId, true);
if (!hasUserId)
{
notification.UserId = null;
}
var requirement = NotificationOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
// TODO
[Theory]
[BitAutoData]
[NotificationCustomize(global: true)]
public async Task HandleAsync_UpdateRequirementGlobalNotification_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = NotificationOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_UpdateRequirementUserNotMatching_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid(), notification.OrganizationId);
notification.OrganizationId = null;
var requirement = NotificationOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_UpdateRequirementOrganizationNotMatching_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, Guid.NewGuid());
var requirement = NotificationOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_UpdateRequirementOrganizationUserNoAccessReportsPermission_Unauthorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, notification.OrganizationId, canAccessReports: false);
var requirement = NotificationOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
[NotificationCustomize(global: false)]
public async Task HandleAsync_UpdateRequirementUserNotPartOfOrganization_Authorized(
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId);
notification.OrganizationId = null;
var requirement = NotificationOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(global: false)]
public async Task HandleAsync_UpdateRequirementOrganizationUserCanAccessReports_Authorized(
bool hasUserId,
SutProvider<NotificationAuthorizationHandler> sutProvider,
Notification notification, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notification.UserId, notification.OrganizationId, true);
if (!hasUserId)
{
notification.UserId = null;
}
var requirement = NotificationOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notification);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
}

View File

@ -0,0 +1,179 @@
#nullable enable
using Bit.Core.Context;
using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.NotificationCenter.Authorization;
using System.Security.Claims;
using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Entities;
using Microsoft.AspNetCore.Authorization;
using NSubstitute;
using Xunit;
[SutProviderCustomize]
[NotificationStatusCustomize]
public class NotificationStatusAuthorizationHandlerTests
{
private static void SetupUserPermission(SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
Guid? userId = null)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_UnsupportedNotificationOperationRequirement_Throws(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = new NotificationStatusOperationsRequirement("UnsupportedOperation");
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(context));
}
[Theory]
[BitAutoData(nameof(NotificationStatusOperations.Read))]
[BitAutoData(nameof(NotificationStatusOperations.Create))]
[BitAutoData(nameof(NotificationStatusOperations.Update))]
public async Task HandleAsync_NotLoggedIn_Unauthorized(
string requirementName,
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, userId: null);
var requirement = new NotificationStatusOperationsRequirement(requirementName);
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData(nameof(NotificationStatusOperations.Read))]
[BitAutoData(nameof(NotificationStatusOperations.Create))]
[BitAutoData(nameof(NotificationStatusOperations.Update))]
public async Task HandleAsync_ResourceEmpty_Unauthorized(
string requirementName,
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = new NotificationStatusOperationsRequirement(requirementName);
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, null);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_ReadRequirementUserNotMatching_Unauthorized(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = NotificationStatusOperations.Read;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_ReadRequirement_Authorized(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notificationStatus.UserId);
var requirement = NotificationStatusOperations.Read;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_CreateRequirementUserNotMatching_Unauthorized(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = NotificationStatusOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_CreateRequirement_Authorized(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notificationStatus.UserId);
var requirement = NotificationStatusOperations.Create;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_UpdateRequirementUserNotMatching_Unauthorized(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, Guid.NewGuid());
var requirement = NotificationStatusOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_UpdateRequirement_Authorized(
SutProvider<NotificationStatusAuthorizationHandler> sutProvider,
NotificationStatus notificationStatus, ClaimsPrincipal claimsPrincipal)
{
SetupUserPermission(sutProvider, notificationStatus.UserId);
var requirement = NotificationStatusOperations.Update;
var context = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, notificationStatus);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
}