diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/Controllers/OrganizationsController.cs index e8a817c9cb..5398352af5 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/Controllers/OrganizationsController.cs @@ -9,6 +9,8 @@ using Bit.Core.Models.Table; using Bit.Core.Utilities; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Models.Business; +using Bit.Core.Enums; namespace Bit.Admin.Controllers { @@ -24,6 +26,7 @@ namespace Bit.Admin.Controllers private readonly IPaymentService _paymentService; private readonly IApplicationCacheService _applicationCacheService; private readonly GlobalSettings _globalSettings; + private readonly IReferenceEventService _referenceEventService; public OrganizationsController( IOrganizationRepository organizationRepository, @@ -34,7 +37,8 @@ namespace Bit.Admin.Controllers IPolicyRepository policyRepository, IPaymentService paymentService, IApplicationCacheService applicationCacheService, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + IReferenceEventService referenceEventService) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -45,6 +49,7 @@ namespace Bit.Admin.Controllers _paymentService = paymentService; _applicationCacheService = applicationCacheService; _globalSettings = globalSettings; + _referenceEventService = referenceEventService; } public async Task Index(string name = null, string userEmail = null, bool? paid = null, @@ -140,6 +145,7 @@ namespace Bit.Admin.Controllers model.ToOrganization(organization); await _organizationRepository.ReplaceAsync(organization); await _applicationCacheService.UpsertOrganizationAbilityAsync(organization); + await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.SalesAssisted, organization)); return RedirectToAction("Edit", new { id }); } diff --git a/src/Core/Context/CurrentContext.cs b/src/Core/Context/CurrentContext.cs index 928c622ebf..e78368356f 100644 --- a/src/Core/Context/CurrentContext.cs +++ b/src/Core/Context/CurrentContext.cs @@ -37,6 +37,7 @@ namespace Bit.Core.Context public virtual bool IsBot { get; set; } public virtual bool MaybeBot { get; set; } public virtual int? BotScore { get; set; } + public virtual string ClientId { get; set; } public CurrentContext(IProviderUserRepository providerUserRepository) { @@ -114,19 +115,19 @@ namespace Bit.Core.Context UserId = subIdGuid; } - var clientId = GetClaimValue(claimsDict, "client_id"); + ClientId = GetClaimValue(claimsDict, "client_id"); var clientSubject = GetClaimValue(claimsDict, "client_sub"); var orgApi = false; if (clientSubject != null) { - if (clientId?.StartsWith("installation.") ?? false) + if (ClientId?.StartsWith("installation.") ?? false) { if (Guid.TryParse(clientSubject, out var idGuid)) { InstallationId = idGuid; } } - else if (clientId?.StartsWith("organization.") ?? false) + else if (ClientId?.StartsWith("organization.") ?? false) { if (Guid.TryParse(clientSubject, out var idGuid)) { diff --git a/src/Core/Context/ICurrentContext.cs b/src/Core/Context/ICurrentContext.cs index 1479fea630..184f91bd18 100644 --- a/src/Core/Context/ICurrentContext.cs +++ b/src/Core/Context/ICurrentContext.cs @@ -24,7 +24,7 @@ namespace Bit.Core.Context bool IsBot { get; set; } bool MaybeBot { get; set; } int? BotScore { get; set; } - + string ClientId { get; set; } Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings); Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings); diff --git a/src/Core/Enums/BitwardenClient.cs b/src/Core/Enums/BitwardenClient.cs new file mode 100644 index 0000000000..f4031cb018 --- /dev/null +++ b/src/Core/Enums/BitwardenClient.cs @@ -0,0 +1,13 @@ +namespace Bit.Core.Enums +{ + public static class BitwardenClient + { + public const string + Web = "web", + Browser = "browser", + Desktop = "desktop", + Mobile = "mobile", + Cli = "cli", + DirectoryConnector = "connector"; + } +} diff --git a/src/Core/Enums/ReferenceEventType.cs b/src/Core/Enums/ReferenceEventType.cs index 5609fafb73..819bce1547 100644 --- a/src/Core/Enums/ReferenceEventType.cs +++ b/src/Core/Enums/ReferenceEventType.cs @@ -28,5 +28,17 @@ namespace Bit.Core.Enums SendCreated, [EnumMember(Value = "send-accessed")] SendAccessed, + [EnumMember(Value = "organization-imported")] + DirectorySynced, + [EnumMember(Value = "vault-imported")] + VaultImported, + [EnumMember(Value = "secret-added")] + CipherCreated, + [EnumMember(Value = "group-created")] + GroupCreated, + [EnumMember(Value = "collection-created")] + CollectionCreated, + [EnumMember(Value = "sales-assisted")] + SalesAssisted } } diff --git a/src/Core/IdentityServer/StaticClientStore.cs b/src/Core/IdentityServer/StaticClientStore.cs index 408673a088..c70598a5ed 100644 --- a/src/Core/IdentityServer/StaticClientStore.cs +++ b/src/Core/IdentityServer/StaticClientStore.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Bit.Core.Settings; +using Bit.Core.Enums; namespace Bit.Core.IdentityServer { @@ -11,12 +12,12 @@ namespace Bit.Core.IdentityServer { ApiClients = new List { - new ApiClient(globalSettings, "mobile", 90, 1), - new ApiClient(globalSettings, "web", 30, 1), - new ApiClient(globalSettings, "browser", 30, 1), - new ApiClient(globalSettings, "desktop", 30, 1), - new ApiClient(globalSettings, "cli", 30, 1), - new ApiClient(globalSettings, "connector", 30, 24) + new ApiClient(globalSettings, BitwardenClient.Mobile, 90, 1), + new ApiClient(globalSettings, BitwardenClient.Web, 30, 1), + new ApiClient(globalSettings, BitwardenClient.Browser, 30, 1), + new ApiClient(globalSettings, BitwardenClient.Desktop, 30, 1), + new ApiClient(globalSettings, BitwardenClient.Cli, 30, 1), + new ApiClient(globalSettings, BitwardenClient.DirectoryConnector, 30, 24) }.ToDictionary(c => c.ClientId); } diff --git a/src/Core/Models/Business/ReferenceEvent.cs b/src/Core/Models/Business/ReferenceEvent.cs index 569b6ee7be..d3bbf3020e 100644 --- a/src/Core/Models/Business/ReferenceEvent.cs +++ b/src/Core/Models/Business/ReferenceEvent.cs @@ -42,7 +42,12 @@ namespace Bit.Core.Models.Business public PlanType? PlanType { get; set; } + public string OldPlanName { get; set; } + + public PlanType? OldPlanType { get; set; } + public int? Seats { get; set; } + public int? PreviousSeats { get; set; } public short? Storage { get; set; } diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index 77726c65fb..318bac4c56 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -13,6 +13,7 @@ using Bit.Core.Enums; using Bit.Core.Utilities; using Bit.Core.Settings; using Bit.Core.Models.Api; +using Bit.Core.Models.Business; namespace Bit.Core.Services { @@ -34,6 +35,7 @@ namespace Bit.Core.Services private readonly IPolicyRepository _policyRepository; private readonly GlobalSettings _globalSettings; private const long _fileSizeLeeway = 1024L * 1024L; // 1MB + private readonly IReferenceEventService _referenceEventService; public CipherService( ICipherRepository cipherRepository, @@ -48,7 +50,8 @@ namespace Bit.Core.Services IEventService eventService, IUserService userService, IPolicyRepository policyRepository, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + IReferenceEventService referenceEventService) { _cipherRepository = cipherRepository; _folderRepository = folderRepository; @@ -63,6 +66,7 @@ namespace Bit.Core.Services _userService = userService; _policyRepository = policyRepository; _globalSettings = globalSettings; + _referenceEventService = referenceEventService; } public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate, @@ -83,6 +87,9 @@ namespace Bit.Core.Services cipher.UserId = savingUserId; } await _cipherRepository.CreateAsync(cipher, collectionIds); + + await _referenceEventService.RaiseEventAsync( + new ReferenceEvent(ReferenceEventType.CipherCreated, await _organizationRepository.GetByIdAsync(cipher.OrganizationId.Value))); } else { @@ -727,17 +734,17 @@ namespace Bit.Core.Services IEnumerable> collectionRelationships, Guid importingUserId) { - if (collections.Count > 0) + var org = collections.Count > 0 ? + await _organizationRepository.GetByIdAsync(collections[0].OrganizationId) : + await _organizationRepository.GetByIdAsync(ciphers.FirstOrDefault(c => c.OrganizationId.HasValue).OrganizationId.Value); + + if (collections.Count > 0 && org != null && org.MaxCollections.HasValue) { - var org = await _organizationRepository.GetByIdAsync(collections[0].OrganizationId); - if (org != null && org.MaxCollections.HasValue) + var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id); + if (org.MaxCollections.Value < (collectionCount + collections.Count)) { - var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id); - if (org.MaxCollections.Value < (collectionCount + collections.Count)) - { - throw new BadRequestException("This organization can only have a maximum of " + - $"{org.MaxCollections.Value} collections."); - } + throw new BadRequestException("This organization can only have a maximum of " + + $"{org.MaxCollections.Value} collections."); } } @@ -777,6 +784,13 @@ namespace Bit.Core.Services // push await _pushService.PushSyncVaultAsync(importingUserId); + + + if (org != null) + { + await _referenceEventService.RaiseEventAsync( + new ReferenceEvent(ReferenceEventType.VaultImported, org)); + } } public async Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false) diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs index e982aff587..829cdead29 100644 --- a/src/Core/Services/Implementations/CollectionService.cs +++ b/src/Core/Services/Implementations/CollectionService.cs @@ -5,6 +5,8 @@ using Bit.Core.Models.Table; using Bit.Core.Repositories; using System.Collections.Generic; using Bit.Core.Models.Data; +using Bit.Core.Models.Business; +using Bit.Core.Enums; namespace Bit.Core.Services { @@ -16,6 +18,7 @@ namespace Bit.Core.Services private readonly ICollectionRepository _collectionRepository; private readonly IUserRepository _userRepository; private readonly IMailService _mailService; + private readonly IReferenceEventService _referenceEventService; public CollectionService( IEventService eventService, @@ -23,7 +26,8 @@ namespace Bit.Core.Services IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository, IUserRepository userRepository, - IMailService mailService) + IMailService mailService, + IReferenceEventService referenceEventService) { _eventService = eventService; _organizationRepository = organizationRepository; @@ -31,6 +35,7 @@ namespace Bit.Core.Services _collectionRepository = collectionRepository; _userRepository = userRepository; _mailService = mailService; + _referenceEventService = referenceEventService; } public async Task SaveAsync(Collection collection, IEnumerable groups = null, @@ -76,6 +81,7 @@ namespace Bit.Core.Services } await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Created); + await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.CollectionCreated, org)); } else { diff --git a/src/Core/Services/Implementations/GroupService.cs b/src/Core/Services/Implementations/GroupService.cs index 1cb300443c..869609b767 100644 --- a/src/Core/Services/Implementations/GroupService.cs +++ b/src/Core/Services/Implementations/GroupService.cs @@ -5,6 +5,8 @@ using Bit.Core.Models.Table; using Bit.Core.Repositories; using System.Collections.Generic; using Bit.Core.Models.Data; +using Bit.Core.Models.Business; +using Bit.Core.Enums; namespace Bit.Core.Services { @@ -14,17 +16,20 @@ namespace Bit.Core.Services private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IGroupRepository _groupRepository; + private readonly IReferenceEventService _referenceEventService; public GroupService( IEventService eventService, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, - IGroupRepository groupRepository) + IGroupRepository groupRepository, + IReferenceEventService referenceEventService) { _eventService = eventService; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _groupRepository = groupRepository; + _referenceEventService = referenceEventService; } public async Task SaveAsync(Group group, IEnumerable collections = null) @@ -54,6 +59,7 @@ namespace Bit.Core.Services } await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created); + await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.GroupCreated, org)); } else { diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index ad969c002f..7fcb08f958 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -305,6 +305,8 @@ namespace Bit.Core.Services { PlanName = newPlan.Name, PlanType = newPlan.Type, + OldPlanName = existingPlan.Name, + OldPlanType = existingPlan.Type, Seats = organization.Seats, Storage = organization.MaxStorageGb, }); @@ -497,14 +499,15 @@ namespace Bit.Core.Services }); } - organization.Seats = (short?)newSeatTotal; await _referenceEventService.RaiseEventAsync( new ReferenceEvent(ReferenceEventType.AdjustSeats, organization) { PlanName = plan.Name, PlanType = plan.Type, - Seats = organization.Seats, + Seats = newSeatTotal, + PreviousSeats = organization.Seats }); + organization.Seats = (short?)newSeatTotal; await ReplaceAndUpdateCache(organization); return paymentIntentClientSecret; } @@ -1977,6 +1980,12 @@ namespace Bit.Core.Services } } } + + if (_currentContext.ClientId == BitwardenClient.DirectoryConnector) + { + await _referenceEventService.RaiseEventAsync( + new ReferenceEvent(ReferenceEventType.DirectorySynced, organization)); + } } public async Task RotateApiKeyAsync(Organization organization)