mirror of
https://github.com/bitwarden/server.git
synced 2025-07-16 07:07:32 -05:00
[PM-14406] Security Task Notifications (#5344)
* initial commit of `CipherOrganizationPermission_GetManyByUserId` * create queries to get all of the security tasks that are actionable by a user - A task is "actionable" when the user has manage permissions for that cipher * rename query * return the user's email from the query as well * Add email notification for at-risk passwords - Added email layouts for security tasks * add push notification for security tasks * update entity framework to match stored procedure plus testing * update date of migration and remove orderby * add push service to security task controller * rename `SyncSecurityTasksCreated` to `SyncNotification` * remove duplicate return * remove unused directive * remove unneeded new notification type * use `createNotificationCommand` to alert all platforms * return the cipher id that is associated with the security task and store the security task id on the notification entry * Add `TaskId` to the output model of `GetUserSecurityTasksByCipherIdsAsync` * move notification logic to command * use TaskId from `_getSecurityTasksNotificationDetailsQuery` * add service * only push last notification for each user * formatting * refactor `CreateNotificationCommand` parameter to `sendPush` * flip boolean in test * update interface to match usage * do not push any of the security related notifications to the user * add `PendingSecurityTasks` push type * add push notification for pending security tasks
This commit is contained in:
@ -348,6 +348,51 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<UserSecurityTaskCipher>> GetUserSecurityTasksByCipherIdsAsync(Guid organizationId, IEnumerable<Core.Vault.Entities.SecurityTask> tasks)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var cipherIds = tasks.Where(t => t.CipherId.HasValue).Select(t => t.CipherId.Value);
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var query = new UserSecurityTasksByCipherIdsQuery(organizationId, cipherIds).Run(dbContext);
|
||||
|
||||
ICollection<UserSecurityTaskCipher> userTaskCiphers;
|
||||
|
||||
// SQLite does not support the GROUP BY clause
|
||||
if (dbContext.Database.IsSqlite())
|
||||
{
|
||||
userTaskCiphers = (await query.ToListAsync())
|
||||
.GroupBy(c => new { c.UserId, c.Email, c.CipherId })
|
||||
.Select(g => new UserSecurityTaskCipher
|
||||
{
|
||||
UserId = g.Key.UserId,
|
||||
Email = g.Key.Email,
|
||||
CipherId = g.Key.CipherId,
|
||||
}).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupByQuery = from p in query
|
||||
group p by new { p.UserId, p.Email, p.CipherId }
|
||||
into g
|
||||
select new UserSecurityTaskCipher
|
||||
{
|
||||
UserId = g.Key.UserId,
|
||||
CipherId = g.Key.CipherId,
|
||||
Email = g.Key.Email,
|
||||
};
|
||||
userTaskCiphers = await groupByQuery.ToListAsync();
|
||||
}
|
||||
|
||||
foreach (var userTaskCipher in userTaskCiphers)
|
||||
{
|
||||
userTaskCipher.TaskId = tasks.First(t => t.CipherId == userTaskCipher.CipherId).Id;
|
||||
}
|
||||
|
||||
return userTaskCiphers;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CipherDetails> GetByIdAsync(Guid id, Guid userId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
|
@ -0,0 +1,71 @@
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Vault.Repositories.Queries;
|
||||
|
||||
public class UserSecurityTasksByCipherIdsQuery : IQuery<UserCipherForTask>
|
||||
{
|
||||
private readonly Guid _organizationId;
|
||||
private readonly IEnumerable<Guid> _cipherIds;
|
||||
|
||||
public UserSecurityTasksByCipherIdsQuery(Guid organizationId, IEnumerable<Guid> cipherIds)
|
||||
{
|
||||
_organizationId = organizationId;
|
||||
_cipherIds = cipherIds;
|
||||
}
|
||||
|
||||
public IQueryable<UserCipherForTask> Run(DatabaseContext dbContext)
|
||||
{
|
||||
var baseCiphers =
|
||||
from c in dbContext.Ciphers
|
||||
where _cipherIds.Contains(c.Id)
|
||||
join o in dbContext.Organizations
|
||||
on c.OrganizationId equals o.Id
|
||||
where o.Id == _organizationId && o.Enabled
|
||||
select c;
|
||||
|
||||
var userPermissions =
|
||||
from c in baseCiphers
|
||||
join cc in dbContext.CollectionCiphers
|
||||
on c.Id equals cc.CipherId
|
||||
join cu in dbContext.CollectionUsers
|
||||
on cc.CollectionId equals cu.CollectionId
|
||||
join ou in dbContext.OrganizationUsers
|
||||
on cu.OrganizationUserId equals ou.Id
|
||||
where ou.OrganizationId == _organizationId
|
||||
&& cu.Manage == true
|
||||
select new { ou.UserId, c.Id };
|
||||
|
||||
var groupPermissions =
|
||||
from c in baseCiphers
|
||||
join cc in dbContext.CollectionCiphers
|
||||
on c.Id equals cc.CipherId
|
||||
join cg in dbContext.CollectionGroups
|
||||
on cc.CollectionId equals cg.CollectionId
|
||||
join gu in dbContext.GroupUsers
|
||||
on cg.GroupId equals gu.GroupId
|
||||
join ou in dbContext.OrganizationUsers
|
||||
on gu.OrganizationUserId equals ou.Id
|
||||
where ou.OrganizationId == _organizationId
|
||||
&& cg.Manage == true
|
||||
&& !userPermissions.Any(up => up.Id == c.Id && up.UserId == ou.UserId)
|
||||
select new { ou.UserId, c.Id };
|
||||
|
||||
return userPermissions.Union(groupPermissions)
|
||||
.Join(
|
||||
dbContext.Users,
|
||||
p => p.UserId,
|
||||
u => u.Id,
|
||||
(p, u) => new { p.UserId, p.Id, u.Email }
|
||||
)
|
||||
.GroupBy(x => new { x.UserId, x.Email, x.Id })
|
||||
.Select(g => new UserCipherForTask
|
||||
{
|
||||
UserId = (Guid)g.Key.UserId,
|
||||
Email = g.Key.Email,
|
||||
CipherId = g.Key.Id
|
||||
})
|
||||
.OrderByDescending(x => x.Email);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user