From 28770d3761657cbcf5a1cbb83ce5bbf643bcc161 Mon Sep 17 00:00:00 2001
From: Kyle Spearrin <kyle.spearrin@gmail.com>
Date: Fri, 1 Dec 2017 16:00:30 -0500
Subject: [PATCH] events for collections, groups, and org users

---
 src/Api/Controllers/CollectionsController.cs  |  2 +-
 src/Api/Controllers/GroupsController.cs       |  2 +-
 .../OrganizationUsersController.cs            |  4 +--
 src/Core/Enums/EventType.cs                   |  5 +++-
 src/Core/Models/Data/CipherEvent.cs           |  2 +-
 src/Core/Models/Data/CollectionEvent.cs       | 23 ++++++++++++++++
 src/Core/Models/Data/EventTableEntity.cs      |  3 +++
 src/Core/Models/Data/GroupEvent.cs            | 23 ++++++++++++++++
 src/Core/Models/Data/OrganizationEvent.cs     | 22 +++++-----------
 src/Core/Models/Data/OrganizationUserEvent.cs | 24 +++++++++++++++++
 src/Core/Services/ICollectionService.cs       |  2 +-
 src/Core/Services/IEventService.cs            |  4 +++
 src/Core/Services/IGroupService.cs            |  1 +
 src/Core/Services/IOrganizationService.cs     |  1 +
 .../Implementations/CollectionService.cs      | 13 ++++++++++
 .../Services/Implementations/EventService.cs  | 26 ++++++++++++++++++-
 .../Services/Implementations/GroupService.cs  | 12 +++++++++
 .../Implementations/OrganizationService.cs    | 14 ++++++++++
 .../NoopImplementations/NoopEventService.cs   | 20 ++++++++++++++
 .../Utilities/ServiceCollectionExtensions.cs  |  6 ++---
 20 files changed, 182 insertions(+), 27 deletions(-)
 create mode 100644 src/Core/Models/Data/CollectionEvent.cs
 create mode 100644 src/Core/Models/Data/GroupEvent.cs
 create mode 100644 src/Core/Models/Data/OrganizationUserEvent.cs

diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs
index cf8571c85c..72e5683216 100644
--- a/src/Api/Controllers/CollectionsController.cs
+++ b/src/Api/Controllers/CollectionsController.cs
@@ -135,7 +135,7 @@ namespace Bit.Api.Controllers
                 throw new NotFoundException();
             }
 
-            await _collectionRepository.DeleteAsync(collection);
+            await _collectionService.DeleteAsync(collection);
         }
 
         [HttpDelete("{id}/user/{orgUserId}")]
diff --git a/src/Api/Controllers/GroupsController.cs b/src/Api/Controllers/GroupsController.cs
index e9eea63a51..b61e0cb5d7 100644
--- a/src/Api/Controllers/GroupsController.cs
+++ b/src/Api/Controllers/GroupsController.cs
@@ -121,7 +121,7 @@ namespace Bit.Api.Controllers
                 throw new NotFoundException();
             }
 
-            await _groupRepository.DeleteAsync(group);
+            await _groupService.DeleteAsync(group);
         }
 
         [HttpDelete("{id}/user/{orgUserId}")]
diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs
index ee5da0dfcf..2af90a40a0 100644
--- a/src/Api/Controllers/OrganizationUsersController.cs
+++ b/src/Api/Controllers/OrganizationUsersController.cs
@@ -178,8 +178,8 @@ namespace Bit.Api.Controllers
             {
                 throw new BadRequestException("Only owners can update other owners.");
             }
-
-            await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, model.GroupIds.Select(g => new Guid(g)));
+            
+            await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)));
         }
 
         [HttpDelete("{id}")]
diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs
index f027a3699a..d6d6080194 100644
--- a/src/Core/Enums/EventType.cs
+++ b/src/Core/Enums/EventType.cs
@@ -29,6 +29,9 @@
         OrganizationUser_Invited = 1500,
         OrganizationUser_Confirmed = 1501,
         OrganizationUser_Updated = 1502,
-        OrganizationUser_Removed = 1503
+        OrganizationUser_Removed = 1503,
+        OrganizationUser_UpdatedGroups = 1504,
+
+        Organization_Updated = 1600
     }
 }
diff --git a/src/Core/Models/Data/CipherEvent.cs b/src/Core/Models/Data/CipherEvent.cs
index 463ceb7c75..ccd0dae962 100644
--- a/src/Core/Models/Data/CipherEvent.cs
+++ b/src/Core/Models/Data/CipherEvent.cs
@@ -7,7 +7,7 @@ namespace Bit.Core.Models.Data
 {
     public class CipherEvent : EventTableEntity
     {
-        public CipherEvent(Cipher cipher, EventType type, Guid? actingUserId = null)
+        public CipherEvent(Cipher cipher, Guid? actingUserId, EventType type)
         {
             OrganizationId = cipher.OrganizationId;
             UserId = cipher.UserId;
diff --git a/src/Core/Models/Data/CollectionEvent.cs b/src/Core/Models/Data/CollectionEvent.cs
new file mode 100644
index 0000000000..64d1765088
--- /dev/null
+++ b/src/Core/Models/Data/CollectionEvent.cs
@@ -0,0 +1,23 @@
+using System;
+using Bit.Core.Enums;
+using Bit.Core.Models.Table;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.Models.Data
+{
+    public class CollectionEvent : EventTableEntity
+    {
+        public CollectionEvent(Collection collection, Guid actingUserId, EventType type)
+        {
+            OrganizationId = collection.OrganizationId;
+            CollectionId = collection.Id;
+            Type = (int)type;
+            ActingUserId = actingUserId;
+
+            Timestamp = DateTime.UtcNow;
+            PartitionKey = $"OrganizationId={OrganizationId}";
+            RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
+                CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type);
+        }
+    }
+}
diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs
index e0e1cf1ece..4285f24431 100644
--- a/src/Core/Models/Data/EventTableEntity.cs
+++ b/src/Core/Models/Data/EventTableEntity.cs
@@ -11,6 +11,9 @@ namespace Bit.Core.Models.Data
         public Guid? OrganizationId { get; set; }
         public Guid? CipherId { get; set; }
         public ICollection<Guid> CipherIds { get; set; }
+        public Guid? CollectionId { get; set; }
+        public Guid? GroupId { get; set; }
+        public Guid? OrganizationUserId { get; set; }
         public Guid? ActingUserId { get; set; }
     }
 }
diff --git a/src/Core/Models/Data/GroupEvent.cs b/src/Core/Models/Data/GroupEvent.cs
new file mode 100644
index 0000000000..14e478abd7
--- /dev/null
+++ b/src/Core/Models/Data/GroupEvent.cs
@@ -0,0 +1,23 @@
+using System;
+using Bit.Core.Enums;
+using Bit.Core.Models.Table;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.Models.Data
+{
+    public class GroupEvent : EventTableEntity
+    {
+        public GroupEvent(Group group, Guid actingUserId, EventType type)
+        {
+            OrganizationId = group.OrganizationId;
+            GroupId = group.Id;
+            Type = (int)type;
+            ActingUserId = actingUserId;
+
+            Timestamp = DateTime.UtcNow;
+            PartitionKey = $"OrganizationId={OrganizationId}";
+            RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
+                CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type);
+        }
+    }
+}
diff --git a/src/Core/Models/Data/OrganizationEvent.cs b/src/Core/Models/Data/OrganizationEvent.cs
index b62fd5affb..886a8a5f51 100644
--- a/src/Core/Models/Data/OrganizationEvent.cs
+++ b/src/Core/Models/Data/OrganizationEvent.cs
@@ -1,32 +1,22 @@
 using System;
 using Bit.Core.Enums;
+using Bit.Core.Models.Table;
 using Bit.Core.Utilities;
 
 namespace Bit.Core.Models.Data
 {
     public class OrganizationEvent : EventTableEntity
     {
-        public OrganizationEvent(Guid organizationId, EventType type)
+        public OrganizationEvent(Organization organization, Guid actingUserId, EventType type)
         {
-            OrganizationId = organizationId;
+            OrganizationId = organization.Id;
             Type = (int)type;
+            ActingUserId = actingUserId;
 
             Timestamp = DateTime.UtcNow;
             PartitionKey = $"OrganizationId={OrganizationId}";
-            RowKey = string.Format("Date={0}__Type={1}",
-                CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), Type);
-        }
-
-        public OrganizationEvent(Guid organizationId, Guid userId, EventType type)
-        {
-            OrganizationId = organizationId;
-            UserId = userId;
-            Type = (int)type;
-
-            Timestamp = DateTime.UtcNow;
-            PartitionKey = $"OrganizationId={OrganizationId}";
-            RowKey = string.Format("Date={0}__UserId={1}__Type={2}",
-                CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), UserId, Type);
+            RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
+                CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type);
         }
     }
 }
diff --git a/src/Core/Models/Data/OrganizationUserEvent.cs b/src/Core/Models/Data/OrganizationUserEvent.cs
new file mode 100644
index 0000000000..0026126ebe
--- /dev/null
+++ b/src/Core/Models/Data/OrganizationUserEvent.cs
@@ -0,0 +1,24 @@
+using System;
+using Bit.Core.Enums;
+using Bit.Core.Models.Table;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.Models.Data
+{
+    public class OrganizationUserEvent : EventTableEntity
+    {
+        public OrganizationUserEvent(OrganizationUser organizationUser, Guid actingUserId, EventType type)
+        {
+            OrganizationId = organizationUser.OrganizationId;
+            UserId = organizationUser.UserId;
+            OrganizationUserId = organizationUser.Id;
+            Type = (int)type;
+            ActingUserId = actingUserId;
+
+            Timestamp = DateTime.UtcNow;
+            PartitionKey = $"OrganizationId={OrganizationId}";
+            RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
+                CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type);
+        }
+    }
+}
diff --git a/src/Core/Services/ICollectionService.cs b/src/Core/Services/ICollectionService.cs
index 8c5363e842..eb72c8fbec 100644
--- a/src/Core/Services/ICollectionService.cs
+++ b/src/Core/Services/ICollectionService.cs
@@ -1,7 +1,6 @@
 using System.Threading.Tasks;
 using Bit.Core.Models.Table;
 using System.Collections.Generic;
-using System;
 using Bit.Core.Models.Data;
 
 namespace Bit.Core.Services
@@ -9,5 +8,6 @@ namespace Bit.Core.Services
     public interface ICollectionService
     {
         Task SaveAsync(Collection collection, IEnumerable<SelectionReadOnly> groups = null);
+        Task DeleteAsync(Collection collection);
     }
 }
diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs
index abc400bf47..61e72be796 100644
--- a/src/Core/Services/IEventService.cs
+++ b/src/Core/Services/IEventService.cs
@@ -9,5 +9,9 @@ namespace Bit.Core.Services
     {
         Task LogUserEventAsync(Guid userId, EventType type);
         Task LogCipherEventAsync(Cipher cipher, EventType type);
+        Task LogCollectionEventAsync(Collection collection, EventType type);
+        Task LogGroupEventAsync(Group group, EventType type);
+        Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type);
+        Task LogOrganizationEventAsync(Organization organization, EventType type);
     }
 }
diff --git a/src/Core/Services/IGroupService.cs b/src/Core/Services/IGroupService.cs
index 602ebc44f5..9be9b43a9b 100644
--- a/src/Core/Services/IGroupService.cs
+++ b/src/Core/Services/IGroupService.cs
@@ -8,5 +8,6 @@ namespace Bit.Core.Services
     public interface IGroupService
     {
         Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null);
+        Task DeleteAsync(Group group);
     }
 }
diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs
index fcd6c51ca5..9a9d795ee2 100644
--- a/src/Core/Services/IOrganizationService.cs
+++ b/src/Core/Services/IOrganizationService.cs
@@ -36,6 +36,7 @@ namespace Bit.Core.Services
         Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<SelectionReadOnly> collections);
         Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId);
         Task DeleteUserAsync(Guid organizationId, Guid userId);
+        Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds);
         Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId);
         Task<OrganizationLicense> GenerateLicenseAsync(Organization organization, Guid installationId);
         Task ImportAsync(Guid organizationId, Guid importingUserId, IEnumerable<ImportedGroup> groups,
diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs
index ff5016d0ed..aa4898757a 100644
--- a/src/Core/Services/Implementations/CollectionService.cs
+++ b/src/Core/Services/Implementations/CollectionService.cs
@@ -10,6 +10,7 @@ namespace Bit.Core.Services
 {
     public class CollectionService : ICollectionService
     {
+        private readonly IEventService _eventService;
         private readonly IOrganizationRepository _organizationRepository;
         private readonly IOrganizationUserRepository _organizationUserRepository;
         private readonly ICollectionRepository _collectionRepository;
@@ -17,12 +18,14 @@ namespace Bit.Core.Services
         private readonly IMailService _mailService;
 
         public CollectionService(
+            IEventService eventService,
             IOrganizationRepository organizationRepository,
             IOrganizationUserRepository organizationUserRepository,
             ICollectionRepository collectionRepository,
             IUserRepository userRepository,
             IMailService mailService)
         {
+            _eventService = eventService;
             _organizationRepository = organizationRepository;
             _organizationUserRepository = organizationUserRepository;
             _collectionRepository = collectionRepository;
@@ -58,6 +61,8 @@ namespace Bit.Core.Services
                 {
                     await _collectionRepository.CreateAsync(collection, groups);
                 }
+
+                await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Created);
             }
             else
             {
@@ -69,7 +74,15 @@ namespace Bit.Core.Services
                 {
                     await _collectionRepository.ReplaceAsync(collection, groups ?? new List<SelectionReadOnly>());
                 }
+
+                await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Updated);
             }
         }
+
+        public async Task DeleteAsync(Collection collection)
+        {
+            await _collectionRepository.DeleteAsync(collection);
+            await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Deleted);
+        }
     }
 }
diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs
index 03e4959e56..80e91475c9 100644
--- a/src/Core/Services/Implementations/EventService.cs
+++ b/src/Core/Services/Implementations/EventService.cs
@@ -63,7 +63,31 @@ namespace Bit.Core.Services
                 return;
             }
 
-            var e = new CipherEvent(cipher, type, _currentContext?.UserId);
+            var e = new CipherEvent(cipher, _currentContext?.UserId, type);
+            await _eventRepository.CreateAsync(e);
+        }
+
+        public async Task LogCollectionEventAsync(Collection collection, EventType type)
+        {
+            var e = new CollectionEvent(collection, _currentContext.UserId.Value, type);
+            await _eventRepository.CreateAsync(e);
+        }
+
+        public async Task LogGroupEventAsync(Group group, EventType type)
+        {
+            var e = new GroupEvent(group, _currentContext.UserId.Value, type);
+            await _eventRepository.CreateAsync(e);
+        }
+
+        public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type)
+        {
+            var e = new OrganizationUserEvent(organizationUser, _currentContext.UserId.Value, type);
+            await _eventRepository.CreateAsync(e);
+        }
+
+        public async Task LogOrganizationEventAsync(Organization organization, EventType type)
+        {
+            var e = new OrganizationEvent(organization, _currentContext.UserId.Value, type);
             await _eventRepository.CreateAsync(e);
         }
     }
diff --git a/src/Core/Services/Implementations/GroupService.cs b/src/Core/Services/Implementations/GroupService.cs
index 0790677449..de8b73cad8 100644
--- a/src/Core/Services/Implementations/GroupService.cs
+++ b/src/Core/Services/Implementations/GroupService.cs
@@ -10,13 +10,16 @@ namespace Bit.Core.Services
 {
     public class GroupService : IGroupService
     {
+        private readonly IEventService _eventService;
         private readonly IOrganizationRepository _organizationRepository;
         private readonly IGroupRepository _groupRepository;
 
         public GroupService(
+            IEventService eventService,
             IOrganizationRepository organizationRepository,
             IGroupRepository groupRepository)
         {
+            _eventService = eventService;
             _organizationRepository = organizationRepository;
             _groupRepository = groupRepository;
         }
@@ -46,12 +49,21 @@ namespace Bit.Core.Services
                 {
                     await _groupRepository.CreateAsync(group, collections);
                 }
+
+                await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created);
             }
             else
             {
                 group.RevisionDate = DateTime.UtcNow;
                 await _groupRepository.ReplaceAsync(group, collections ?? new List<SelectionReadOnly>());
+                await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated);
             }
         }
+
+        public async Task DeleteAsync(Group group)
+        {
+            await _groupRepository.DeleteAsync(group);
+            await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Deleted);
+        }
     }
 }
diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs
index b595a0521f..488a2489df 100644
--- a/src/Core/Services/Implementations/OrganizationService.cs
+++ b/src/Core/Services/Implementations/OrganizationService.cs
@@ -29,6 +29,7 @@ namespace Bit.Core.Services
         private readonly IPushRegistrationService _pushRegistrationService;
         private readonly IDeviceRepository _deviceRepository;
         private readonly ILicensingService _licensingService;
+        private readonly IEventService _eventService;
         private readonly IInstallationRepository _installationRepository;
         private readonly StripePaymentService _stripePaymentService;
         private readonly GlobalSettings _globalSettings;
@@ -45,6 +46,7 @@ namespace Bit.Core.Services
             IPushRegistrationService pushRegistrationService,
             IDeviceRepository deviceRepository,
             ILicensingService licensingService,
+            IEventService eventService,
             IInstallationRepository installationRepository,
             GlobalSettings globalSettings)
         {
@@ -59,6 +61,7 @@ namespace Bit.Core.Services
             _pushRegistrationService = pushRegistrationService;
             _deviceRepository = deviceRepository;
             _licensingService = licensingService;
+            _eventService = eventService;
             _installationRepository = installationRepository;
             _stripePaymentService = new StripePaymentService();
             _globalSettings = globalSettings;
@@ -803,6 +806,7 @@ namespace Bit.Core.Services
             }
 
             await _organizationRepository.ReplaceAsync(organization);
+            await _eventService.LogOrganizationEventAsync(organization, EventType.Organization_Updated);
 
             if(updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
             {
@@ -1008,6 +1012,7 @@ namespace Bit.Core.Services
             orgUser.Key = key;
             orgUser.Email = null;
             await _organizationUserRepository.ReplaceAsync(orgUser);
+            await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
 
             var user = await _userRepository.GetByIdAsync(orgUser.UserId.Value);
             await _mailService.SendOrganizationConfirmedEmailAsync(org.Name, user.Email);
@@ -1057,6 +1062,7 @@ namespace Bit.Core.Services
                 collections = new List<SelectionReadOnly>();
             }
             await _organizationUserRepository.ReplaceAsync(user, collections);
+            await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated);
         }
 
         public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId)
@@ -1088,6 +1094,7 @@ namespace Bit.Core.Services
             }
 
             await _organizationUserRepository.DeleteAsync(orgUser);
+            await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
 
             if(orgUser.UserId.HasValue)
             {
@@ -1112,6 +1119,7 @@ namespace Bit.Core.Services
             }
 
             await _organizationUserRepository.DeleteAsync(orgUser);
+            await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
 
             if(orgUser.UserId.HasValue)
             {
@@ -1121,6 +1129,12 @@ namespace Bit.Core.Services
             }
         }
 
+        public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds)
+        {
+            await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
+            await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
+        }
+
         public async Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId)
         {
             var organization = await _organizationRepository.GetByIdAsync(organizationId);
diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs
index d10933ee62..2afd4c5837 100644
--- a/src/Core/Services/NoopImplementations/NoopEventService.cs
+++ b/src/Core/Services/NoopImplementations/NoopEventService.cs
@@ -12,6 +12,26 @@ namespace Bit.Core.Services
             return Task.FromResult(0);
         }
 
+        public Task LogCollectionEventAsync(Collection collection, EventType type)
+        {
+            return Task.FromResult(0);
+        }
+
+        public Task LogGroupEventAsync(Group group, EventType type)
+        {
+            return Task.FromResult(0);
+        }
+
+        public Task LogOrganizationEventAsync(Organization organization, EventType type)
+        {
+            return Task.FromResult(0);
+        }
+
+        public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type)
+        {
+            return Task.FromResult(0);
+        }
+
         public Task LogUserEventAsync(Guid userId, EventType type)
         {
             return Task.FromResult(0);
diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs
index 0875267b3c..6aff835a7b 100644
--- a/src/Core/Utilities/ServiceCollectionExtensions.cs
+++ b/src/Core/Utilities/ServiceCollectionExtensions.cs
@@ -57,9 +57,9 @@ namespace Bit.Core.Utilities
             services.AddScoped<ICipherService, CipherService>();
             services.AddScoped<IUserService, UserService>();
             services.AddSingleton<IDeviceService, DeviceService>();
-            services.AddSingleton<IOrganizationService, OrganizationService>();
-            services.AddSingleton<ICollectionService, CollectionService>();
-            services.AddSingleton<IGroupService, GroupService>();
+            services.AddScoped<IOrganizationService, OrganizationService>();
+            services.AddScoped<ICollectionService, CollectionService>();
+            services.AddScoped<IGroupService, GroupService>();
             services.AddScoped<Services.IEventService, EventService>();
         }