1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-18 02:11:22 +01:00
bitwarden-server/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs
Rui Tomé 828566d879
[AC-1126] Flexible collections: Deprecate manager role (#3422)
* [AC-1117] Add manage permission (#3126)

* Update sql files to add Manage permission

* Add migration script

* Rename collection manage migration file to remove duplicate migration date

* Migrations

* Add manage to models

* Add manage to repository

* Add constraint to Manage columns

* Migration lint fixes

* Add manage to OrganizationUserUserDetails_ReadWithCollectionsById

* Add missing manage fields

* Add 'Manage' to UserCollectionDetails

* Use CREATE OR ALTER where possible

* [AC-1374] Limit collection creation/deletion to Owner/Admin (#3145)

* feat: update org table with new column, write migration, refs AC-1374

* feat: update views with new column, refs AC-1374

* feat: Alter sprocs (org create/update) to include new column, refs AC-1374

* feat: update entity/data/request/response models to handle new column, refs AC-1374

* feat: update necessary Provider related views during migration, refs AC-1374

* fix: update org create to default new column to false, refs AC-1374

* feat: added new API/request model for collection management and removed property from update request model, refs AC-1374

* fix: renamed migration script to be after secrets manage beta column changes, refs AC-1374

* fix: dotnet format, refs AC-1374

* feat: add ef migrations to reflect mssql changes, refs AC-1374

* fix: dotnet format, refs AC-1374

* feat: update API signature to accept Guid and explain Cd verbiage, refs AC-1374

* fix: merge conflict resolution

* [AC-1174] CollectionUser and CollectionGroup authorization handlers (#3194)

* [AC-1174] Introduce BulkAuthorizationHandler.cs

* [AC-1174] Introduce CollectionUserAuthorizationHandler

* [AC-1174] Add CreateForNewCollection CollectionUser requirement

* [AC-1174] Add some more details to CollectionCustomization

* [AC-1174] Formatting

* [AC-1174] Add CollectionGroupOperation.cs

* [AC-1174] Introduce CollectionGroupAuthorizationHandler.cs

* [AC-1174] Cleanup CollectionFixture customization

Implement and use re-usable extension method to support seeded Guids

* [AC-1174] Introduce WithValueFromList AutoFixtureExtensions

Modify CollectionCustomization to use multiple organization Ids for auto generated test data

* [AC-1174] Simplify CollectionUserAuthorizationHandler.cs

Modify the authorization handler to only perform authorization logic. Validation logic will need to be handled by any calling commands/controllers instead.

* [AC-1174] Introduce shared CollectionAccessAuthorizationHandlerBase

A shared base authorization handler was created for both CollectionUser and CollectionGroup resources, as they share the same underlying management authorization logic.

* [AC-1174] Update CollectionUserAuthorizationHandler and CollectionGroupAuthorizationHandler to use the new CollectionAccessAuthorizationHandlerBase class

* [AC-1174] Formatting

* [AC-1174] Cleanup typo and redundant ToList() call

* [AC-1174] Add check for provider users

* [AC-1174] Reduce nested loops

* [AC-1174] Introduce ICollectionAccess.cs

* [AC-1174] Remove individual CollectionGroup and CollectionUser auth handlers and use base class instead

* [AC-1174] Tweak unit test to fail minimally

* [AC-1174] Reorganize authorization handlers in Core project

* [AC-1174] Introduce new AddCoreAuthorizationHandlers() extension method

* [AC-1174] Move CollectionAccessAuthorizationHandler into Api project

* [AC-1174] Move CollectionFixture to Vault folder

* [AC-1174] Rename operation to CreateUpdateDelete

* [AC-1174] Require single organization for collection access authorization handler

- Add requirement that all target collections must belong to the same organization
- Simplify logic related to multiple organizations
- Update tests and helpers
- Use ToHashSet to improve lookup time

* [AC-1174] Fix null reference exception

* [AC-1174] Throw bad request exception when collections belong to different organizations

* [AC-1174] Switch to CollectionAuthorizationHandler instead of CollectionAccessAuthorizationHandler to reduce complexity

* Fix improper merge conflict resolution

* fix: add permission check for collection management api, refs AC-1647 (#3252)

* [AC-1125] Enforce org setting for creating/deleting collections (#3241)

* [AC-1117] Add manage permission (#3126)

* Update sql files to add Manage permission

* Add migration script

* Rename collection manage migration file to remove duplicate migration date

* Migrations

* Add manage to models

* Add manage to repository

* Add constraint to Manage columns

* Migration lint fixes

* Add manage to OrganizationUserUserDetails_ReadWithCollectionsById

* Add missing manage fields

* Add 'Manage' to UserCollectionDetails

* Use CREATE OR ALTER where possible

* [AC-1374] Limit collection creation/deletion to Owner/Admin (#3145)

* feat: update org table with new column, write migration, refs AC-1374

* feat: update views with new column, refs AC-1374

* feat: Alter sprocs (org create/update) to include new column, refs AC-1374

* feat: update entity/data/request/response models to handle new column, refs AC-1374

* feat: update necessary Provider related views during migration, refs AC-1374

* fix: update org create to default new column to false, refs AC-1374

* feat: added new API/request model for collection management and removed property from update request model, refs AC-1374

* fix: renamed migration script to be after secrets manage beta column changes, refs AC-1374

* fix: dotnet format, refs AC-1374

* feat: add ef migrations to reflect mssql changes, refs AC-1374

* fix: dotnet format, refs AC-1374

* feat: update API signature to accept Guid and explain Cd verbiage, refs AC-1374

* feat: created collection auth handler/operations, added LimitCollectionCdOwnerAdmin to CurrentContentOrganization, refs AC-1125

* feat: create vault service collection extensions and register with base services, refs AC-1125

* feat: deprecated CurrentContext.CreateNewCollections, refs AC-1125

* feat: deprecate DeleteAnyCollection for single resource usages, refs AC-1125

* feat: move service registration to api, update references, refs AC-1125

* feat: add bulk delete authorization handler, refs AC-1125

* feat: always assign user and give manage access on create, refs AC-1125

* fix: updated CurrentContextOrganization type, refs AC-1125

* feat: combined existing collection authorization handlers/operations, refs AC-1125

* fix: OrganizationServiceTests -> CurrentContentOrganization typo, refs AC-1125

* fix: format, refs AC-1125

* fix: update collection controller tests, refs AC-1125

* fix: dotnet format, refs AC-1125

* feat: removed extra BulkAuthorizationHandler, refs AC-1125

* fix: dotnet format, refs AC-1125

* fix: change string to guid for org id, update bulk delete request model, refs AC-1125

* fix: remove delete many collection check, refs AC-1125

* fix: clean up collection auth handler, refs AC-1125

* fix: format fix for CollectionOperations, refs AC-1125

* fix: removed unnecessary owner check, add org null check to custom permission validation, refs AC-1125

* fix: remove unused methods in CurrentContext, refs AC-1125

* fix: removed obsolete test, fixed failling delete many test, refs AC-1125

* fix: CollectionAuthorizationHandlerTests fixes, refs AC-1125

* fix: OrganizationServiceTests fix broken test by mocking GetOrganization, refs AC-1125

* fix: CollectionAuthorizationHandler - remove unused repository, refs AC-1125

* feat: moved UserId null check to common method, refs AC-1125

* fix: updated auth handler tests to remove dependency on requirement for common code checks, refs AC-1125

* feat: updated conditionals/comments for create/delete methods within colleciton auth handler, refs AC-1125

* feat: added create/delete collection auth handler success methods, refs AC-1125

* fix: new up permissions to prevent excessive null checks, refs AC-1125

* fix: remove old reference to CreateNewCollections, refs AC-1125

* fix: typo within ViewAssignedCollections method, refs AC-1125

---------

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>

* refactor: remove organizationId from CollectionBulkDeleteRequestModel, refs AC-1649 (#3282)

* [AC-1174] Bulk Collection Management (#3229)

* [AC-1174] Update SelectionReadOnlyRequestModel to use Guid for Id property

* [AC-1174] Introduce initial bulk-access collection endpoint

* [AC-1174] Introduce BulkAddCollectionAccessCommand and validation logic/tests

* [AC-1174] Add CreateOrUpdateAccessMany method to CollectionRepository

* [AC-1174] Add event logs for bulk add collection access command

* [AC-1174] Add User_BumpAccountRevisionDateByCollectionIds and database migration script

* [AC-1174] Implement EF repository method

* [AC-1174] Improve null checks

* [AC-1174] Remove unnecessary BulkCollectionAccessRequestModel helpers

* [AC-1174] Add unit tests for new controller endpoint

* [AC-1174] Fix formatting

* [AC-1174] Remove comment

* [AC-1174] Remove redundant organizationId parameter

* [AC-1174] Ensure user and group Ids are distinct

* [AC-1174] Cleanup tests based on PR feedback

* [AC-1174] Formatting

* [AC-1174] Update CollectionGroup alias in the sproc

* [AC-1174] Add some additional comments to SQL sproc

* [AC-1174] Add comment explaining additional SaveChangesAsync call

---------

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>

* [AC-1646] Rename LimitCollectionCdOwnerAdmin column (#3300)

* Rename LimitCollectionCdOwnerAdmin -> LimitCollectionCreationDeletion

* Rename and bump migration script

* [AC-1666] Removed EditAnyCollection from Create/Delete permission checks (#3301)

* fix: remove EditAnyCollection from Create/Delete permission check, refs AC-1666

* fix: updated comment, refs AC-1666

* Add feature flags constants and flag new route

* Update feature flag keys

* Create LegacyCollectionsAuthorizationHandler and start to re-implement old logic

* [AC-1669] Bug - Remove obsolete assignUserId from CollectionService.SaveAsync(...) (#3312)

* fix: remove AssignUserId from CollectionService.SaveAsync, refs AC-1669

* fix: add manage access conditional before creating collection, refs AC-1669

* fix: move access logic for create/update, fix all tests, refs AC-1669

* fix: add CollectionAccessSelection fixture, update tests, update bad reqeuest message, refs AC-1669

* fix: format, refs AC-1669

* fix: update null params with specific arg.is null checks, refs Ac-1669

* fix: update attribute class name, refs AC-1669

* Revert "Create LegacyCollectionsAuthorizationHandler and start to re-implement old logic"

This reverts commit fbb19cdadd.

* Restore old logic behind flags

* Add missing flags

* Fix logic, add comment

* Fix tests

* Add EnableFeatureFlag extension method for tests

* Restore legacy tests

* Add FeatureServiceFixtures to set feature flags in test

* Remove unused method

* Fix formatting

* Set feature flag to ON for auth handler tests

* Use fixture instead of calling nsubstitute directly

* Change FlexibleCollectionsIsEnabled method to property

Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>

* Finish changing to property

* [AC-1139] Marked as obsolete the methods EditAssignedCollections, DeleteAssignedCollections and ViewAssignedCollections on ICurrentContext

* [AC-1139] Disabled the ability to set the custom permissions 'Delete/Edit Assigned Collections' if flexible collections feature flag is enabled

* [AC-1713] [Flexible collections] Add feature flags to server (#3334)

* Add feature flags for FlexibleCollections and BulkCollectionAccess

* Flag new routes and behaviour

---------

Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>

* [AC-1748] Updated CurrentContext EditAssignedCollections, DeleteAssignedCollections, ViewAssignedCollections to check for flexible collections feature flag

* [AC-1748] Created GroupAuthorizationHandler and modified GroupsController.Get to use it if flexible collections feature flag is enabled

* [AC-1748] Created OrganizationUserAuthorizationHandler and modified OrganizationUsersController.Get to use that if flexible collections feature flag is enabled

* [AC-1748] Reverted changes on OrganizationService

* [AC-1748] Removed GroupAuthorizationHandler

* [AC-1748] Set resource as null when reading OrganizationUserUserDetailsResponseModel list

* [AC-1139] Updated CollectionsController GetManyWithDetails and Get to check for flexible collections flag

* [AC-1139] Modified CollectionsController.Get to check access before getting collections

* [AC-1139] Updated CollectionsController to use CollectionAuthorizationHandler in all endpoints if flag is enabled

* [AC-1139] Lining up collection access data with Manage = true if feature flag is off

* Add joint codeownership for auth handlers (#3346)

* [AC-1139] Separated flexible collections logic from old logic in CollectionsController; Refactored CollectionAuthorizationHandler

* [AC-1139] Fixed formatting on OrganizationUsersController; renamed OrganizationUserOperations.Read to ReadAll

* [AC-1748] Fixed logic to set manage = true for collections if user has EditAssignedCollection permission

* [AC-1717] Update default values for LimitCollectionCreationDeletion (#3365)

* Change default value in organization create sproc to 1

* Drop old column name still present in some QA instances

* Set LimitCollectionCreationDeletion value in code based on feature flag

* Fix: add missing namespace after merging in master

* Fix: add missing namespace after merging in master

* [AC-1683] Fix DB migrations for new Manage permission (#3307)

* [AC-1683] Update migration script and introduce V2 procedures and types

* [AC-1683] Update repository calls to use new V2 procedures / types

* [AC-1684] Update bulk add collection migration script to use new V2 type

* [AC-1683] Undo Manage changes to more original procedures

* [AC-1683] Restore whitespace changes

* [AC-1683] Clarify comments regarding explicit column lists

* [AC-1683] Update migration script dates

* [AC-1683] Split the migration script for readability

* [AC-1683] Re-name SelectReadOnlyArray_V2 to CollectionAccessSelectionType

* [AC-1139] Added permission checks for GroupsController.Get if FC feature flag is enabled

* [AC-1139] Added an AuthorizationHandler for Collections and renamed existing to BulkCollectionAuthorizationHandler

* [AC-1648] [Flexible Collections] Bump migration scripts before feature branch merge (#3371)

* Bump dates on sql migration scripts

* Bump date on ef migrations

* [AC-1139] Renamed existing CollectionAuthorizationHandler to BulkCollectionAuthorizationHandler for collections and created CollectionAuthorizationHandler for single item access. Fixed unit tests and created more

* [AC-1139] Fixed Provider AuthorizationHandler logic for Groups and OrganizationUsers

* [AC-1139] Fixed CollectionAuthorizationHandler unit tests

* [AC-1139] Added unit tests for GroupAuthorizationHandler and OrganizationUserAuthorizationHandler

* [AC-1139] Added unit test to test setting users with EditAssignedCollections with Manage permission when saving a collection

* [AC-1139] Added unit tests for OrganizationService InviteUser and SaveUser with EditAssignedCollections = true

* [AC-1139] Reverted changes on OrganizationService

* [AC-1139] Marked obsolete Permissions EditAssignedCollections and DeleteAssignedCollections

* [AC-1139] Renamed FlexibleCollectionsIsEnabled properties to UseFlexibleCollections

* [AC-1139] Renamed new flexible collections controller methods to have 'vNext' in the name to indicate its a new version

* [AC-1139] Created AuthorizationServiceExtensions to have an extension method for AuthorizeAsync where the resource is null

* [AC-1139] Renamed CollectionsController method to delete collection users from 'Delete' to 'DeleteUser'

* [AC-1139] Refactored BulkCollectionAuthorizationHandler.CheckCollectionPermissionsAsync

* [AC-1139] Created new CollectionOperation ReadAccess and changed GetUsers_vNext to use it

* [AC-1139] Created new CollectionOperationRequirement ReadAllWithAccess

* [AC-1139] Addressing PR suggestions

* [AC-1139] Unit tests refactors and added tests

* [AC-1139] Updated BulkCollectionAuthorizationHandler to not fail if the resource list is null or empty.

* [AC-1139] Modified authorization handlers to not fail in case the resource is null

* [AC-1139] Reverted changes made to CollectionService and OrganizationService

* [AC-1139] Reverted changes to CollectionServiceTests and OrganizationServiceTests

* [AC-1126] Deprecated ICurrentContext.OrganizationManager and Reject any attempt to assign the Manager role to an existing user or via invite

* [AC-1126] Reverted change on ignoring Manager claims; modified CurrentContext.OrganizationManager to throw exception when feature flag is enabled

* [AC-1139] Fixed OrganizationUser.ReadAll permissions

* [AC-1139] Fixed Groups ReadAll permissions

* [AC-1139] Fixed unit tests

* [AC-1139] Removed unnecessary permission from GroupAuthorizationHandler

* [AC-1139] Rewrote GroupAuthorizationHandler to be similar to other AuthHandlers; Revisited unit tests

* [AC-1139] Rewrote OrganizationUserAuthorizationHandler to be similar to other AuthHandlers; Revisited unit tests

* [AC-1139] Changed CollectionsController.Get_vNext method to use CollectionRepository.GetByIdAsync without userId

* [AC-1139] Changed GroupAuthorizationHandler and OrganizationUserAuthorizationHandler to fail if no OrganizationId is passed as a parameter

* [AC-1139] Rewrote CollectionAuthorizationHandler to be similar to other AuthHandlers; Revisited unit tests

* [AC-1139] Resolved conflict when resolving operations between CollectionOperations and BulkCollectionOperations

* [AC-1139] Created BulkCollectionOperations.ReadWithAccess

* [AC-1139] Removed unnecessary permissions object creation on unit tests

* [AC-1139] Refactored unit test

* [AC-1139] Renamed UseFlexibleCollections variables to FlexibleCollectionsIsEnabled

* [AC-1139] Added missing read permission check

* [AC-1139] Added CollectionOperation ReadManyWithDetails

* [AC-1139] Removed unnecessary operation

* [AC-1139] Throwing NotFoundException on GetManyWithDetails_vNext if user does not have read permissions

* Revert "[AC-1139] Removed unnecessary permissions object creation on unit tests"

This reverts commit b20d75b232.

* [AC-1139] Refined permissions for BulkCollectionOperations.Read

* [AC-1139] Revised BulkCollectionAuthorizationHandler permissions for Read and ReadWithAccess

* [AC-1139] Removed duplicate IOrganizationUserRepository

* [AC-1139] Added ManageGroups permission access for CollectionOperations.ReadAll

* [AC-1139] Added ManageUsers permission access for CollectionOperations.ReadAllWithAccess

* [AC-1139] Filter returned collections by manage permission

* [AC-1139] Refactor Read authorization checks in BulkCollectionAuthorizationHandler to no longer check for the `LimitCollectionCreationDeletion` property

* [AC-1126] Rename property name for better readability

* Update src/Core/AdminConsole/Services/Implementations/OrganizationService.cs

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* [AC-1126] Fixed manager check

---------

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com>
Co-authored-by: Shane Melton <smelton@bitwarden.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-12-16 11:44:10 +00:00

2078 lines
109 KiB
C#

using System.Text.Json;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Business;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Core.Tokens;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Fakes;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using NSubstitute.ReturnsExtensions;
using Xunit;
using Organization = Bit.Core.AdminConsole.Entities.Organization;
using OrganizationUser = Bit.Core.Entities.OrganizationUser;
using Policy = Bit.Core.AdminConsole.Entities.Policy;
namespace Bit.Core.Test.Services;
[SutProviderCustomize]
public class OrganizationServiceTests
{
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task OrgImportCreateNewUsers(SutProvider<OrganizationService> sutProvider, Guid userId,
Organization org, List<OrganizationUserUserDetails> existingUsers, List<ImportedOrganizationUser> newUsers)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
sutProvider.Create();
org.UseDirectory = true;
org.Seats = 10;
newUsers.Add(new ImportedOrganizationUser
{
Email = existingUsers.First().Email,
ExternalId = existingUsers.First().ExternalId
});
var expectedNewUsersCount = newUsers.Count - 1;
existingUsers.First().Type = OrganizationUserType.Owner;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyDetailsByOrganizationAsync(org.Id)
.Returns(existingUsers);
sutProvider.GetDependency<IOrganizationUserRepository>().GetCountByOrganizationIdAsync(org.Id)
.Returns(existingUsers.Count);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyByOrganizationAsync(org.Id, OrganizationUserType.Owner)
.Returns(existingUsers.Select(u => new OrganizationUser { Status = OrganizationUserStatusType.Confirmed, Type = OrganizationUserType.Owner, Id = u.Id }).ToList());
sutProvider.GetDependency<ICurrentContext>().ManageUsers(org.Id).Returns(true);
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
.CreateToken(Arg.Any<OrganizationUser>())
.Returns(
info => new OrgUserInviteTokenable(info.Arg<OrganizationUser>())
{
ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5))
}
);
await sutProvider.Sut.ImportAsync(org.Id, userId, null, newUsers, null, false);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == 0));
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
// Create new users
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
await sutProvider.GetDependency<IMailService>().Received(1)
.BulkSendOrganizationInviteEmailAsync(org.Name,
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount), org.PlanType == PlanType.Free);
// Send events
await sutProvider.GetDependency<IEventService>().Received(1)
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
events.Count() == expectedNewUsersCount));
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.InvitedUsers && referenceEvent.Id == org.Id &&
referenceEvent.Users == expectedNewUsersCount));
}
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task OrgImportCreateNewUsersAndMarryExistingUser(SutProvider<OrganizationService> sutProvider,
Guid userId, Organization org, List<OrganizationUserUserDetails> existingUsers,
List<ImportedOrganizationUser> newUsers)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
sutProvider.Create();
org.UseDirectory = true;
org.Seats = newUsers.Count + existingUsers.Count + 1;
var reInvitedUser = existingUsers.First();
reInvitedUser.ExternalId = null;
newUsers.Add(new ImportedOrganizationUser
{
Email = reInvitedUser.Email,
ExternalId = reInvitedUser.Email,
});
var expectedNewUsersCount = newUsers.Count - 1;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyDetailsByOrganizationAsync(org.Id)
.Returns(existingUsers);
sutProvider.GetDependency<IOrganizationUserRepository>().GetCountByOrganizationIdAsync(org.Id)
.Returns(existingUsers.Count);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(reInvitedUser.Id)
.Returns(new OrganizationUser { Id = reInvitedUser.Id });
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyByOrganizationAsync(org.Id, OrganizationUserType.Owner)
.Returns(existingUsers.Select(u => new OrganizationUser { Status = OrganizationUserStatusType.Confirmed, Type = OrganizationUserType.Owner, Id = u.Id }).ToList());
var currentContext = sutProvider.GetDependency<ICurrentContext>();
currentContext.ManageUsers(org.Id).Returns(true);
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
.CreateToken(Arg.Any<OrganizationUser>())
.Returns(
info => new OrgUserInviteTokenable(info.Arg<OrganizationUser>())
{
ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5))
}
);
await sutProvider.Sut.ImportAsync(org.Id, userId, null, newUsers, null, false);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default, default);
// Upserted existing user
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == 1));
// Created and invited new users
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
await sutProvider.GetDependency<IMailService>().Received(1)
.BulkSendOrganizationInviteEmailAsync(org.Name,
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount), org.PlanType == PlanType.Free);
// Sent events
await sutProvider.GetDependency<IEventService>().Received(1)
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
events.Where(e => e.Item2 == EventType.OrganizationUser_Invited).Count() == expectedNewUsersCount));
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.InvitedUsers && referenceEvent.Id == org.Id &&
referenceEvent.Users == expectedNewUsersCount));
}
[Theory]
[BitAutoData(PlanType.FamiliesAnnually)]
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.Plan = planType;
var plan = StaticStore.GetPlan(signup.Plan);
signup.AdditionalSeats = 0;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.UseSecretsManager = false;
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
var result = await sutProvider.Sut.SignUpAsync(signup);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
Arg.Is<Organization>(o =>
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == null
&& o.SmServiceAccounts == null));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.Signup &&
referenceEvent.PlanName == plan.Name &&
referenceEvent.PlanType == plan.Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
Assert.NotNull(result);
Assert.NotNull(result.Item1);
Assert.NotNull(result.Item2);
Assert.IsType<Tuple<Organization, OrganizationUser>>(result);
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
plan,
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
signup.TaxInfo,
false,
signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault()
);
}
[Theory]
[BitAutoData(PlanType.EnterpriseAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly)]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.Plan = planType;
var plan = StaticStore.GetPlan(signup.Plan);
signup.UseSecretsManager = true;
signup.AdditionalSeats = 15;
signup.AdditionalSmSeats = 10;
signup.AdditionalServiceAccounts = 20;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
var result = await sutProvider.Sut.SignUpAsync(signup);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
Arg.Is<Organization>(o =>
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.Signup &&
referenceEvent.PlanName == plan.Name &&
referenceEvent.PlanType == plan.Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
Assert.NotNull(result);
Assert.NotNull(result.Item1);
Assert.NotNull(result.Item2);
Assert.IsType<Tuple<Organization, OrganizationUser>>(result);
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
Arg.Is<Plan>(plan),
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
signup.TaxInfo,
false,
signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault()
);
}
[Theory]
[BitAutoData(PlanType.EnterpriseAnnually)]
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.Plan = planType;
signup.UseSecretsManager = true;
signup.AdditionalSeats = 15;
signup.AdditionalSmSeats = 10;
signup.AdditionalServiceAccounts = 20;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpAsync(signup, true));
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.AdditionalSmSeats = 0;
signup.AdditionalSeats = 0;
signup.Plan = PlanType.Free;
signup.UseSecretsManager = true;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.AdditionalServiceAccounts = 10;
signup.AdditionalStorageGb = 0;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SignUpAsync(signup));
Assert.Contains("Plan does not allow additional Service Accounts.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.AdditionalSmSeats = 100;
signup.AdditionalSeats = 10;
signup.Plan = PlanType.EnterpriseAnnually;
signup.UseSecretsManager = true;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.AdditionalServiceAccounts = 10;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SignUpAsync(signup));
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
}
[Theory]
[BitAutoData]
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.AdditionalSmSeats = 10;
signup.AdditionalSeats = 10;
signup.Plan = PlanType.EnterpriseAnnually;
signup.UseSecretsManager = true;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.AdditionalServiceAccounts = -10;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SignUpAsync(signup));
Assert.Contains("You can't subtract Service Accounts!", exception.Message);
}
[Theory]
[OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Owner), BitAutoData]
public async Task InviteUser_NoEmails_Throws(Organization organization, OrganizationUser invitor,
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
{
invite.Emails = null;
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
}
[Theory]
[OrganizationInviteCustomize, BitAutoData]
public async Task InviteUser_DuplicateEmails_PassesWithoutDuplicates(Organization organization, OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
sutProvider.Create();
invite.Emails = invite.Emails.Append(invite.Emails.First());
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
.CreateToken(Arg.Any<OrganizationUser>())
.Returns(
info => new OrgUserInviteTokenable(info.Arg<OrganizationUser>())
{
ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5))
}
);
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) });
await sutProvider.GetDependency<IMailService>().Received(1)
.BulkSendOrganizationInviteEmailAsync(organization.Name,
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invite.Emails.Distinct().Count()), organization.PlanType == PlanType.Free);
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Admin,
InvitorUserType = OrganizationUserType.Owner
), BitAutoData]
public async Task InviteUser_NoOwner_Throws(Organization organization, OrganizationUser invitor,
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Owner,
InvitorUserType = OrganizationUserType.Admin
), BitAutoData]
public async Task InviteUser_NonOwnerConfiguringOwner_Throws(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
currentContext.OrganizationAdmin(organization.Id).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("only an owner", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Custom,
InvitorUserType = OrganizationUserType.User
), BitAutoData]
public async Task InviteUser_NonAdminConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
currentContext.OrganizationUser(organization.Id).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("your account does not have permission to manage users", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Custom,
InvitorUserType = OrganizationUserType.Admin
), BitAutoData]
public async Task InviteUser_WithCustomType_WhenUseCustomPermissionsIsFalse_Throws(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = false;
invite.Permissions = null;
invitor.Status = OrganizationUserStatusType.Confirmed;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
currentContext.OrganizationOwner(organization.Id).Returns(true);
currentContext.ManageUsers(organization.Id).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("to enable custom permissions", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Custom,
InvitorUserType = OrganizationUserType.Admin
), BitAutoData]
public async Task InviteUser_WithCustomType_WhenUseCustomPermissionsIsTrue_Passes(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
organization.Seats = 10;
organization.UseCustomPermissions = true;
invite.Permissions = null;
invitor.Status = OrganizationUserStatusType.Confirmed;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
currentContext.OrganizationOwner(organization.Id).Returns(true);
currentContext.ManageUsers(organization.Id).Returns(true);
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) });
}
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Manager)]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.User)]
public async Task InviteUser_WithNonCustomType_WhenUseCustomPermissionsIsFalse_Passes(OrganizationUserType inviteUserType, Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
organization.Seats = 10;
organization.UseCustomPermissions = false;
invite.Type = inviteUserType;
invite.Permissions = null;
invitor.Status = OrganizationUserStatusType.Confirmed;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
currentContext.OrganizationOwner(organization.Id).Returns(true);
currentContext.ManageUsers(organization.Id).Returns(true);
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) });
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Manager,
InvitorUserType = OrganizationUserType.Custom
), BitAutoData]
public async Task InviteUser_CustomUserWithoutManageUsersConfiguringUser_Throws(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = false },
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
currentContext.OrganizationCustom(organization.Id).Returns(true);
currentContext.ManageUsers(organization.Id).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("account does not have permission", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Admin,
InvitorUserType = OrganizationUserType.Custom
), BitAutoData]
public async Task InviteUser_CustomUserConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
currentContext.OrganizationCustom(organization.Id).Returns(true);
currentContext.ManageUsers(organization.Id).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("can not manage admins", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Owner
), BitAutoData]
public async Task InviteUser_NoPermissionsObject_Passes(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
invite.Permissions = null;
invitor.Status = OrganizationUserStatusType.Confirmed;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
currentContext.OrganizationOwner(organization.Id).Returns(true);
currentContext.ManageUsers(organization.Id).Returns(true);
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) });
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Custom
), BitAutoData]
public async Task InviteUser_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
sutProvider.Create();
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
currentContext.ManageUsers(organization.Id).Returns(true);
currentContext.AccessReports(organization.Id).Returns(true);
currentContext.ManageGroups(organization.Id).Returns(true);
currentContext.ManagePolicies(organization.Id).Returns(true);
currentContext.ManageScim(organization.Id).Returns(true);
currentContext.ManageSso(organization.Id).Returns(true);
currentContext.AccessEventLogs(organization.Id).Returns(true);
currentContext.AccessImportExport(organization.Id).Returns(true);
currentContext.DeleteAssignedCollections(organization.Id).Returns(true);
currentContext.EditAnyCollection(organization.Id).Returns(true);
currentContext.EditAssignedCollections(organization.Id).Returns(true);
currentContext.ManageResetPassword(organization.Id).Returns(true);
currentContext.GetOrganization(organization.Id)
.Returns(new CurrentContextOrganization()
{
Permissions = new Permissions
{
CreateNewCollections = true,
DeleteAnyCollection = true
}
});
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
.CreateToken(Arg.Any<OrganizationUser>())
.Returns(
info => new OrgUserInviteTokenable(info.Arg<OrganizationUser>())
{
ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5))
}
);
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, invites);
await sutProvider.GetDependency<IMailService>().Received(1)
.BulkSendOrganizationInviteEmailAsync(organization.Name,
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invites.SelectMany(i => i.invite.Emails).Count()), organization.PlanType == PlanType.Free);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Custom
), BitAutoData]
public async Task InviteUser_WithEventSystemUser_Passes(Organization organization, EventSystemUser eventSystemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
sutProvider.Create();
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
currentContext.ManageUsers(organization.Id).Returns(true);
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
.CreateToken(Arg.Any<OrganizationUser>())
.Returns(
info => new OrgUserInviteTokenable(info.Arg<OrganizationUser>())
{
ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5))
}
);
await sutProvider.Sut.InviteUsersAsync(organization.Id, eventSystemUser, invites);
await sutProvider.GetDependency<IMailService>().Received(1)
.BulkSendOrganizationInviteEmailAsync(organization.Name,
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invites.SelectMany(i => i.invite.Emails).Count()), organization.PlanType == PlanType.Free);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>());
}
[Theory, BitAutoData, OrganizationInviteCustomize]
public async Task InviteUser_WithSecretsManager_Passes(Organization organization,
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
OrganizationUser savingUser, SutProvider<OrganizationService> sutProvider)
{
organization.PlanType = PlanType.EnterpriseAnnually;
InviteUserHelper_ArrangeValidPermissions(organization, savingUser, sutProvider);
// Set up some invites to grant access to SM
invites.First().invite.AccessSecretsManager = true;
var invitedSmUsers = invites.First().invite.Emails.Count();
foreach (var (invite, externalId) in invites.Skip(1))
{
invite.AccessSecretsManager = false;
}
// Assume we need to add seats for all invited SM users
sutProvider.GetDependency<ICountNewSmSeatsRequiredQuery>()
.CountNewSmSeatsRequiredAsync(organization.Id, invitedSmUsers).Returns(invitedSmUsers);
await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, invites);
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>().Received(1)
.UpdateSubscriptionAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(update =>
update.SmSeats == organization.SmSeats + invitedSmUsers &&
!update.SmServiceAccountsChanged &&
!update.MaxAutoscaleSmSeatsChanged &&
!update.MaxAutoscaleSmSeatsChanged));
}
[Theory, BitAutoData, OrganizationInviteCustomize]
public async Task InviteUser_WithSecretsManager_WhenErrorIsThrown_RevertsAutoscaling(Organization organization,
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
OrganizationUser savingUser, SutProvider<OrganizationService> sutProvider)
{
var initialSmSeats = organization.SmSeats;
InviteUserHelper_ArrangeValidPermissions(organization, savingUser, sutProvider);
// Set up some invites to grant access to SM
invites.First().invite.AccessSecretsManager = true;
var invitedSmUsers = invites.First().invite.Emails.Count();
foreach (var (invite, externalId) in invites.Skip(1))
{
invite.AccessSecretsManager = false;
}
// Assume we need to add seats for all invited SM users
sutProvider.GetDependency<ICountNewSmSeatsRequiredQuery>()
.CountNewSmSeatsRequiredAsync(organization.Id, invitedSmUsers).Returns(invitedSmUsers);
// Mock SecretsManagerSubscriptionUpdateCommand to actually change the organization's subscription in memory
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
.UpdateSubscriptionAsync(Arg.Any<SecretsManagerSubscriptionUpdate>())
.ReturnsForAnyArgs(Task.FromResult(0)).AndDoes(x => organization.SmSeats += invitedSmUsers);
// Throw error at the end of the try block
sutProvider.GetDependency<IReferenceEventService>().RaiseEventAsync(default).ThrowsForAnyArgs<BadRequestException>();
await Assert.ThrowsAsync<AggregateException>(async () => await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, invites));
// OrgUser is reverted
// Note: we don't know what their guids are so comparing length is the best we can do
var invitedEmails = invites.SelectMany(i => i.invite.Emails);
sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).DeleteManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == invitedEmails.Count()));
Received.InOrder(() =>
{
// Initial autoscaling
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
.UpdateSubscriptionAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(update =>
update.SmSeats == initialSmSeats + invitedSmUsers &&
!update.SmServiceAccountsChanged &&
!update.MaxAutoscaleSmSeatsChanged &&
!update.MaxAutoscaleSmSeatsChanged));
// Revert autoscaling
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
.UpdateSubscriptionAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(update =>
update.SmSeats == initialSmSeats &&
!update.SmServiceAccountsChanged &&
!update.MaxAutoscaleSmSeatsChanged &&
!update.MaxAutoscaleSmSeatsChanged));
});
}
[Theory, BitAutoData]
public async Task InviteUser_WithFCEnabled_WhenInvitingManager_Throws(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
invite.Type = OrganizationUserType.Manager;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.FlexibleCollections, Arg.Any<ICurrentContext>())
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.ManageUsers(organization.Id)
.Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("manager role is deprecated", exception.Message.ToLowerInvariant());
}
private void InviteUserHelper_ArrangeValidPermissions(Organization organization, OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
}
[Theory, BitAutoData]
public async Task SaveUser_NoUserId_Throws(OrganizationUser user, Guid? savingUserId,
IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups, SutProvider<OrganizationService> sutProvider)
{
user.Id = default(Guid);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(user, savingUserId, collections, groups));
Assert.Contains("invite the user first", exception.Message.ToLowerInvariant());
}
[Theory, BitAutoData]
public async Task SaveUser_NoChangeToData_Throws(OrganizationUser user, Guid? savingUserId,
IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(user.Id).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(user, savingUserId, collections, groups));
Assert.Contains("make changes before saving", exception.Message.ToLowerInvariant());
}
[Theory, BitAutoData]
public async Task SaveUser_Passes(
Organization organization,
OrganizationUser oldUserData,
OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
Permissions permissions,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
organizationUserRepository.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { savingUser });
currentContext.OrganizationOwner(savingUser.OrganizationId).Returns(true);
await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections, groups);
}
[Theory, BitAutoData]
public async Task SaveUser_WithCustomType_WhenUseCustomPermissionsIsFalse_Throws(
Organization organization,
OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = false;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Permissions = null;
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
organizationUserRepository.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { savingUser });
currentContext.OrganizationOwner(savingUser.OrganizationId).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections, groups));
Assert.Contains("to enable custom permissions", exception.Message.ToLowerInvariant());
}
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Manager)]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.User)]
public async Task SaveUser_WithNonCustomType_WhenUseCustomPermissionsIsFalse_Passes(
OrganizationUserType newUserType,
Organization organization,
OrganizationUser oldUserData,
OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
Permissions permissions,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = false;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Type = newUserType;
newUserData.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
organizationUserRepository.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { savingUser });
currentContext.OrganizationOwner(savingUser.OrganizationId).Returns(true);
await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections, groups);
}
[Theory, BitAutoData]
public async Task SaveUser_WithCustomType_WhenUseCustomPermissionsIsTrue_Passes(
Organization organization,
OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
Permissions permissions,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
organizationUserRepository.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { savingUser });
currentContext.OrganizationOwner(savingUser.OrganizationId).Returns(true);
await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections, groups);
}
[Theory, BitAutoData]
public async Task SaveUser_WithCustomPermission_WhenSavingUserHasCustomPermission_Passes(
Organization organization,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser savingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationOwner,
SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organizationOwner.OrganizationId = organization.Id;
newUserData.Permissions = JsonSerializer.Serialize(new Permissions { AccessReports = true }, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
organizationUserRepository.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { organizationOwner });
currentContext.OrganizationCustom(savingUser.OrganizationId).Returns(true);
currentContext.ManageUsers(savingUser.OrganizationId).Returns(true);
currentContext.AccessReports(savingUser.OrganizationId).Returns(true);
currentContext.GetOrganization(savingUser.OrganizationId).Returns(
new CurrentContextOrganization()
{
Permissions = new Permissions
{
AccessReports = true
}
});
await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections, groups);
}
[Theory, BitAutoData]
public async Task SaveUser_WithCustomPermission_WhenSavingUserDoesNotHaveCustomPermission_Throws(
Organization organization,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Permissions = JsonSerializer.Serialize(new Permissions { AccessReports = true }, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
currentContext.OrganizationCustom(savingUser.OrganizationId).Returns(true);
currentContext.ManageUsers(savingUser.OrganizationId).Returns(true);
currentContext.AccessReports(savingUser.OrganizationId).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections, groups));
Assert.Contains("custom users can only grant the same custom permissions that they have", exception.Message.ToLowerInvariant());
}
[Theory, BitAutoData]
public async Task SaveUser_WithCustomPermission_WhenUpgradingToAdmin_Throws(
Organization organization,
[OrganizationUser(type: OrganizationUserType.Custom)] OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
SutProvider<OrganizationService> sutProvider)
{
organization.UseCustomPermissions = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Permissions = JsonSerializer.Serialize(new Permissions { AccessReports = true }, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
currentContext.OrganizationCustom(oldUserData.OrganizationId).Returns(true);
currentContext.ManageUsers(oldUserData.OrganizationId).Returns(true);
currentContext.AccessReports(oldUserData.OrganizationId).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(newUserData, oldUserData.UserId, collections, groups));
Assert.Contains("custom users can not manage admins or owners", exception.Message.ToLowerInvariant());
}
[Theory, BitAutoData]
public async Task SaveUser_WithFCEnabled_WhenUpgradingToManager_Throws(
Organization organization,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.Manager)] OrganizationUser newUserData,
IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.FlexibleCollections, Arg.Any<ICurrentContext>())
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.ManageUsers(organization.Id)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(oldUserData.Id)
.Returns(oldUserData);
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = oldUserData.OrganizationId = organization.Id;
newUserData.Permissions = CoreHelpers.ClassToJsonData(new Permissions());
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(newUserData, oldUserData.UserId, collections, groups));
Assert.Contains("manager role is deprecated", exception.Message.ToLowerInvariant());
}
[Theory, BitAutoData]
public async Task DeleteUser_InvalidUser(OrganizationUser organizationUser, OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUserAsync(Guid.NewGuid(), organizationUser.Id, deletingUser.UserId));
Assert.Contains("User not valid.", exception.Message);
}
[Theory, BitAutoData]
public async Task DeleteUser_RemoveYourself(OrganizationUser deletingUser, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, deletingUser.Id, deletingUser.UserId));
Assert.Contains("You cannot remove yourself.", exception.Message);
}
[Theory, BitAutoData]
public async Task DeleteUser_NonOwnerRemoveOwner(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
currentContext.OrganizationAdmin(deletingUser.OrganizationId).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId));
Assert.Contains("Only owners can delete other owners.", exception.Message);
}
[Theory, BitAutoData]
public async Task DeleteUser_LastOwner(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { organizationUser });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, null));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory, BitAutoData]
public async Task DeleteUser_Success(
OrganizationUser organizationUser,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { deletingUser, organizationUser });
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
}
[Theory, BitAutoData]
public async Task DeleteUser_WithEventSystemUser_Success(
OrganizationUser organizationUser,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser, EventSystemUser eventSystemUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { deletingUser, organizationUser });
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, eventSystemUser);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
}
[Theory, BitAutoData]
public async Task DeleteUsers_FilterInvalid(OrganizationUser organizationUser, OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { organizationUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId));
Assert.Contains("Users invalid.", exception.Message);
}
[Theory, BitAutoData]
public async Task DeleteUsers_RemoveYourself(
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { deletingUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(default, default).ReturnsForAnyArgs(new[] { orgUser });
var result = await sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
Assert.Contains("You cannot remove yourself.", result[0].Item2);
}
[Theory, BitAutoData]
public async Task DeleteUsers_NonOwnerRemoveOwner(
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
[OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser2,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(default, default).ReturnsForAnyArgs(new[] { orgUser2 });
var result = await sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
Assert.Contains("Only owners can delete other owners.", result[0].Item2);
}
[Theory, BitAutoData]
public async Task DeleteUsers_LastOwner(
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { orgUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(orgUser.OrganizationId, OrganizationUserType.Owner).Returns(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteUsersAsync(orgUser.OrganizationId, organizationUserIds, null));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory, BitAutoData]
public async Task DeleteUsers_Success(
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1, orgUser2 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { deletingUser, orgUser1 });
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
}
[Theory, BitAutoData]
public async Task ConfirmUser_InvalidStatus(OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Invited)] OrganizationUser orgUser, string key,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var userService = Substitute.For<IUserService>();
organizationUserRepository.GetByIdAsync(orgUser.Id).Returns(orgUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService));
Assert.Contains("User not valid.", exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUser_WrongOrganization(OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, string key,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var userService = Substitute.For<IUserService>();
organizationUserRepository.GetByIdAsync(orgUser.Id).Returns(orgUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(confirmingUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService));
Assert.Contains("User not valid.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task ConfirmUserToFree_AlreadyFreeAdminOrOwner_Throws(OrganizationUserType userType, Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var userService = Substitute.For<IUserService>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
org.PlanType = PlanType.Free;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
orgUser.Type = userType;
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(orgUser.UserId.Value).Returns(1);
organizationRepository.GetByIdAsync(org.Id).Returns(org);
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService));
Assert.Contains("User can only be an admin of one free organization.", exception.Message);
}
[Theory]
[BitAutoData(PlanType.Custom, OrganizationUserType.Admin)]
[BitAutoData(PlanType.Custom, OrganizationUserType.Owner)]
[BitAutoData(PlanType.EnterpriseAnnually, OrganizationUserType.Admin)]
[BitAutoData(PlanType.EnterpriseAnnually, OrganizationUserType.Owner)]
[BitAutoData(PlanType.EnterpriseAnnually2020, OrganizationUserType.Admin)]
[BitAutoData(PlanType.EnterpriseAnnually2020, OrganizationUserType.Owner)]
[BitAutoData(PlanType.EnterpriseAnnually2019, OrganizationUserType.Admin)]
[BitAutoData(PlanType.EnterpriseAnnually2019, OrganizationUserType.Owner)]
[BitAutoData(PlanType.EnterpriseMonthly, OrganizationUserType.Admin)]
[BitAutoData(PlanType.EnterpriseMonthly, OrganizationUserType.Owner)]
[BitAutoData(PlanType.EnterpriseMonthly2020, OrganizationUserType.Admin)]
[BitAutoData(PlanType.EnterpriseMonthly2020, OrganizationUserType.Owner)]
[BitAutoData(PlanType.EnterpriseMonthly2019, OrganizationUserType.Admin)]
[BitAutoData(PlanType.EnterpriseMonthly2019, OrganizationUserType.Owner)]
[BitAutoData(PlanType.FamiliesAnnually, OrganizationUserType.Admin)]
[BitAutoData(PlanType.FamiliesAnnually, OrganizationUserType.Owner)]
[BitAutoData(PlanType.FamiliesAnnually2019, OrganizationUserType.Admin)]
[BitAutoData(PlanType.FamiliesAnnually2019, OrganizationUserType.Owner)]
[BitAutoData(PlanType.TeamsAnnually, OrganizationUserType.Admin)]
[BitAutoData(PlanType.TeamsAnnually, OrganizationUserType.Owner)]
[BitAutoData(PlanType.TeamsAnnually2020, OrganizationUserType.Admin)]
[BitAutoData(PlanType.TeamsAnnually2020, OrganizationUserType.Owner)]
[BitAutoData(PlanType.TeamsAnnually2019, OrganizationUserType.Admin)]
[BitAutoData(PlanType.TeamsAnnually2019, OrganizationUserType.Owner)]
[BitAutoData(PlanType.TeamsMonthly, OrganizationUserType.Admin)]
[BitAutoData(PlanType.TeamsMonthly, OrganizationUserType.Owner)]
[BitAutoData(PlanType.TeamsMonthly2020, OrganizationUserType.Admin)]
[BitAutoData(PlanType.TeamsMonthly2020, OrganizationUserType.Owner)]
[BitAutoData(PlanType.TeamsMonthly2019, OrganizationUserType.Admin)]
[BitAutoData(PlanType.TeamsMonthly2019, OrganizationUserType.Owner)]
public async Task ConfirmUserToNonFree_AlreadyFreeAdminOrOwner_DoesNotThrow(PlanType planType, OrganizationUserType orgUserType, Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var userService = Substitute.For<IUserService>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
org.PlanType = planType;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
orgUser.Type = orgUserType;
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(orgUser.UserId.Value).Returns(1);
organizationRepository.GetByIdAsync(org.Id).Returns(org);
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.Name, user.Email);
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1));
}
[Theory, BitAutoData]
public async Task ConfirmUser_SingleOrgPolicy(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser orgUserAnotherOrg, [Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy,
string key, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
var userService = Substitute.For<IUserService>();
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Accepted;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = orgUserAnotherOrg.UserId = user.Id;
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg });
organizationRepository.GetByIdAsync(org.Id).Returns(org);
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
policyRepository.GetManyByOrganizationIdAsync(org.Id).Returns(new[] { singleOrgPolicy });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService));
Assert.Contains("User is a member of another organization.", exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUser_TwoFactorPolicy(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser orgUserAnotherOrg, [Policy(PolicyType.TwoFactorAuthentication)] Policy twoFactorPolicy,
string key, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
var userService = Substitute.For<IUserService>();
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = orgUserAnotherOrg.UserId = user.Id;
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg });
organizationRepository.GetByIdAsync(org.Id).Returns(org);
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
policyRepository.GetManyByOrganizationIdAsync(org.Id).Returns(new[] { twoFactorPolicy });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService));
Assert.Contains("User does not have two-step login enabled.", exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUser_Success(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
[Policy(PolicyType.TwoFactorAuthentication)] Policy twoFactorPolicy,
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy, string key, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
var userService = Substitute.For<IUserService>();
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
organizationRepository.GetByIdAsync(org.Id).Returns(org);
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
policyRepository.GetManyByOrganizationIdAsync(org.Id).Returns(new[] { twoFactorPolicy, singleOrgPolicy });
userService.TwoFactorIsEnabledAsync(user).Returns(true);
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService);
}
[Theory, BitAutoData]
public async Task ConfirmUsers_Success(Organization org,
OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser1,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser2,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser3,
OrganizationUser anotherOrgUser, User user1, User user2, User user3,
[Policy(PolicyType.TwoFactorAuthentication)] Policy twoFactorPolicy,
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy, string key, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var userRepository = sutProvider.GetDependency<IUserRepository>();
var userService = Substitute.For<IUserService>();
org.PlanType = PlanType.EnterpriseAnnually;
orgUser1.OrganizationId = orgUser2.OrganizationId = orgUser3.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser1.UserId = user1.Id;
orgUser2.UserId = user2.Id;
orgUser3.UserId = user3.Id;
anotherOrgUser.UserId = user3.Id;
var orgUsers = new[] { orgUser1, orgUser2, orgUser3 };
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(orgUsers);
organizationRepository.GetByIdAsync(org.Id).Returns(org);
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user1, user2, user3 });
policyRepository.GetManyByOrganizationIdAsync(org.Id).Returns(new[] { twoFactorPolicy, singleOrgPolicy });
userService.TwoFactorIsEnabledAsync(user1).Returns(true);
userService.TwoFactorIsEnabledAsync(user2).Returns(false);
userService.TwoFactorIsEnabledAsync(user3).Returns(true);
organizationUserRepository.GetManyByManyUsersAsync(default)
.ReturnsForAnyArgs(new[] { orgUser1, orgUser2, orgUser3, anotherOrgUser });
var keys = orgUsers.ToDictionary(ou => ou.Id, _ => key);
var result = await sutProvider.Sut.ConfirmUsersAsync(confirmingUser.OrganizationId, keys, confirmingUser.Id, userService);
Assert.Contains("", result[0].Item2);
Assert.Contains("User does not have two-step login enabled.", result[1].Item2);
Assert.Contains("User is a member of another organization.", result[2].Item2);
}
[Theory, BitAutoData]
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPassword_Throws(Guid orgId, string publicKey,
string privateKey, SutProvider<OrganizationService> sutProvider)
{
var currentContext = Substitute.For<ICurrentContext>();
currentContext.ManageResetPassword(orgId).Returns(false);
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => sutProvider.Sut.UpdateOrganizationKeysAsync(orgId, publicKey, privateKey));
}
[Theory, BitAutoData]
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Throws(Organization org, string publicKey,
string privateKey, SutProvider<OrganizationService> sutProvider)
{
var currentContext = sutProvider.GetDependency<ICurrentContext>();
currentContext.ManageResetPassword(org.Id).Returns(true);
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(org.Id).Returns(org);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey));
Assert.Contains("Organization Keys already exist", exception.Message);
}
[Theory, BitAutoData]
public async Task UpdateOrganizationKeysAsync_KeysAlreadySet_Success(Organization org, string publicKey,
string privateKey, SutProvider<OrganizationService> sutProvider)
{
org.PublicKey = null;
org.PrivateKey = null;
var currentContext = sutProvider.GetDependency<ICurrentContext>();
currentContext.ManageResetPassword(org.Id).Returns(true);
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(org.Id).Returns(org);
await sutProvider.Sut.UpdateOrganizationKeysAsync(org.Id, publicKey, privateKey);
}
[Theory]
[PaidOrganizationCustomize(CheckedPlanType = PlanType.EnterpriseAnnually)]
[BitAutoData("Cannot set max seat autoscaling below seat count", 1, 0, 2)]
[BitAutoData("Cannot set max seat autoscaling below seat count", 4, -1, 6)]
public async Task Enterprise_UpdateSubscription_BadInputThrows(string expectedMessage,
int? maxAutoscaleSeats, int seatAdjustment, int? currentSeats, Organization organization, SutProvider<OrganizationService> sutProvider)
=> await UpdateSubscription_BadInputThrows(expectedMessage, maxAutoscaleSeats, seatAdjustment, currentSeats, organization, sutProvider);
[Theory]
[FreeOrganizationCustomize]
[BitAutoData("Your plan does not allow seat autoscaling", 10, 0, null)]
public async Task Free_UpdateSubscription_BadInputThrows(string expectedMessage,
int? maxAutoscaleSeats, int seatAdjustment, int? currentSeats, Organization organization, SutProvider<OrganizationService> sutProvider)
=> await UpdateSubscription_BadInputThrows(expectedMessage, maxAutoscaleSeats, seatAdjustment, currentSeats, organization, sutProvider);
private async Task UpdateSubscription_BadInputThrows(string expectedMessage,
int? maxAutoscaleSeats, int seatAdjustment, int? currentSeats, Organization organization, SutProvider<OrganizationService> sutProvider)
{
organization.Seats = currentSeats;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscription(organization.Id,
seatAdjustment, maxAutoscaleSeats));
Assert.Contains(expectedMessage, exception.Message);
}
[Theory, BitAutoData]
public async Task UpdateSubscription_NoOrganization_Throws(Guid organizationId, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns((Organization)null);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateSubscription(organizationId, 0, null));
}
[Theory, PaidOrganizationCustomize]
[BitAutoData(0, 100, null, true, "")]
[BitAutoData(0, 100, 100, true, "")]
[BitAutoData(0, null, 100, true, "")]
[BitAutoData(1, 100, null, true, "")]
[BitAutoData(1, 100, 100, false, "Seat limit has been reached")]
public async Task CanScaleAsync(int seatsToAdd, int? currentSeats, int? maxAutoscaleSeats,
bool expectedResult, string expectedFailureMessage, Organization organization,
SutProvider<OrganizationService> sutProvider)
{
organization.Seats = currentSeats;
organization.MaxAutoscaleSeats = maxAutoscaleSeats;
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).ReturnsNull();
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, seatsToAdd);
if (expectedFailureMessage == string.Empty)
{
Assert.Empty(failureMessage);
}
else
{
Assert.Contains(expectedFailureMessage, failureMessage);
}
Assert.Equal(expectedResult, result);
}
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task CanScaleAsync_FailsOnSelfHosted(Organization organization,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, 10);
Assert.False(result);
Assert.Contains("Cannot autoscale on self-hosted instance", failureMessage);
}
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task CanScaleAsync_FailsOnResellerManagedOrganization(
Organization organization,
SutProvider<OrganizationService> sutProvider)
{
var provider = new Provider
{
Enabled = true,
Type = ProviderType.Reseller
};
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, 10);
Assert.False(result);
Assert.Contains("Seat limit has been reached. Contact your provider to purchase additional seats.", failureMessage);
}
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task Delete_Success(Organization organization, SutProvider<OrganizationService> sutProvider)
{
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
await sutProvider.Sut.DeleteAsync(organization);
await organizationRepository.Received().DeleteAsync(organization);
await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id);
}
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider<OrganizationService> sutProvider,
SsoConfig ssoConfig)
{
ssoConfig.Enabled = true;
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
var ssoConfigRepository = sutProvider.GetDependency<ISsoConfigRepository>();
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.DeleteAsync(organization));
Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message);
await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default);
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
}
private void RestoreRevokeUser_Setup(Organization organization, OrganizationUser owner, OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationUser.OrganizationId).Returns(organization);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetManyByOrganizationAsync(organizationUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { owner });
}
[Theory, BitAutoData]
public async Task RevokeUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
[OrganizationUser] OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
{
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
await sutProvider.Sut.RevokeUserAsync(organizationUser, owner.Id);
await organizationUserRepository.Received().RevokeAsync(organizationUser.Id);
await eventService.Received()
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
}
[Theory, BitAutoData]
public async Task RevokeUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
[OrganizationUser] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
{
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
await sutProvider.Sut.RevokeUserAsync(organizationUser, eventSystemUser);
await organizationUserRepository.Received().RevokeAsync(organizationUser.Id);
await eventService.Received()
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, eventSystemUser);
}
[Theory, BitAutoData]
public async Task RestoreUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
{
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
var userService = Substitute.For<IUserService>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService);
await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited);
await eventService.Received()
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
}
[Theory, BitAutoData]
public async Task RestoreUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
{
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
var userService = Substitute.For<IUserService>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
await sutProvider.Sut.RestoreUserAsync(organizationUser, eventSystemUser, userService);
await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited);
await eventService.Received()
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_WithConfirmedOwner_ReturnsTrue(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_ExcludingConfirmedOwner_ReturnsFalse(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid> { owner.Id }, true);
Assert.False(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_WithInvitedOwner_ReturnsFalse(Organization organization, [OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
Assert.False(result);
}
[Theory]
[BitAutoData(true)]
[BitAutoData(false)]
public async Task HasConfirmedOwnersExcept_WithConfirmedProviderUser_IncludeProviderTrue_ReturnsTrue(bool includeProvider, Organization organization, ProviderUser providerUser, SutProvider<OrganizationService> sutProvider)
{
providerUser.Status = ProviderUserStatusType.Confirmed;
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organization.Id, ProviderUserStatusType.Confirmed)
.Returns(new List<ProviderUser> { providerUser });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), includeProvider);
Assert.Equal(includeProvider, result);
}
[Theory]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
[BitAutoData(PlanType.TeamsStarter)]
[BitAutoData(PlanType.EnterpriseAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = 0,
AdditionalServiceAccounts = 5,
AdditionalSeats = 2
};
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
Assert.Contains("You do not have any Secrets Manager seats!", exception.Message);
}
[Theory]
[BitAutoData(PlanType.Free)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = -1,
AdditionalServiceAccounts = 5
};
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
Assert.Contains("You can't subtract Secrets Manager seats!", exception.Message);
}
[Theory]
[BitAutoData(PlanType.Free)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenPlanDoesNotAllowAdditionalServiceAccounts(
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = 2,
AdditionalServiceAccounts = 5,
AdditionalSeats = 3
};
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
Assert.Contains("Plan does not allow additional Service Accounts.", exception.Message);
}
[Theory]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
[BitAutoData(PlanType.EnterpriseAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = 4,
AdditionalServiceAccounts = 5,
AdditionalSeats = 3
};
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats.", exception.Message);
}
[Theory]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
[BitAutoData(PlanType.TeamsStarter)]
[BitAutoData(PlanType.EnterpriseAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingServiceAccounts(
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = 4,
AdditionalServiceAccounts = -5,
AdditionalSeats = 5
};
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
Assert.Contains("You can't subtract Service Accounts!", exception.Message);
}
[Theory]
[BitAutoData(PlanType.Free)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenPlanDoesNotAllowAdditionalUsers(
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = 2,
AdditionalServiceAccounts = 0,
AdditionalSeats = 5
};
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
Assert.Contains("Plan does not allow additional users.", exception.Message);
}
[Theory]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
[BitAutoData(PlanType.TeamsStarter)]
[BitAutoData(PlanType.EnterpriseAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ValidPlan_NoExceptionThrown(
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
AdditionalSmSeats = 2,
AdditionalServiceAccounts = 0,
AdditionalSeats = 4
};
sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup);
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Owner,
InvitorUserType = OrganizationUserType.Admin
), BitAutoData]
public async Task ValidateOrganizationUserUpdatePermissions_WithAdminAddingOwner_Throws(
Guid organizationId,
OrganizationUserInvite organizationUserInvite,
SutProvider<OrganizationService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
Assert.Contains("only an owner can configure another owner's account.", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Admin,
InvitorUserType = OrganizationUserType.Owner
), BitAutoData]
public async Task ValidateOrganizationUserUpdatePermissions_WithoutManageUsersPermission_Throws(
Guid organizationId,
OrganizationUserInvite organizationUserInvite,
SutProvider<OrganizationService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
Assert.Contains("your account does not have permission to manage users.", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Admin,
InvitorUserType = OrganizationUserType.Custom
), BitAutoData]
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomAddingAdmin_Throws(
Guid organizationId,
OrganizationUserInvite organizationUserInvite,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organizationId).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
Assert.Contains("custom users can not manage admins or owners.", exception.Message.ToLowerInvariant());
}
[Theory]
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.Custom,
InvitorUserType = OrganizationUserType.Custom
), BitAutoData]
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomAddingUser_WithoutPermissions_Throws(
Guid organizationId,
OrganizationUserInvite organizationUserInvite,
SutProvider<OrganizationService> sutProvider)
{
var invitePermissions = new Permissions { AccessReports = true };
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessReports(organizationId).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, invitePermissions));
Assert.Contains("custom users can only grant the same custom permissions that they have.", exception.Message.ToLowerInvariant());
}
}