mirror of
https://github.com/bitwarden/server.git
synced 2024-12-21 16:47:35 +01:00
[PM-14376] Add GET tasks endpoint (#5089)
* Added CQRS pattern * Added the GetManyByUserIdAsync signature to the repositiory * Added sql sproc Created user defined type to hold status Created migration file * Added ef core query * Added absract and concrete implementation for GetManyByUserIdStatusAsync * Added integration tests * Updated params to status * Implemented new query to utilize repository method * Added controller for the security task endpoint * Fixed lint issues * Added documentation * simplified to require single status modified script to check for users with edit rights * Updated ef core query * Added new assertions * simplified to require single status * fixed formatting * Fixed sql script * Removed default null * Added security tasks feature flag
This commit is contained in:
parent
03dde0d008
commit
a332a69112
40
src/Api/Vault/Controllers/SecurityTaskController.cs
Normal file
40
src/Api/Vault/Controllers/SecurityTaskController.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.Vault.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Queries;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.Vault.Controllers;
|
||||
|
||||
[Route("tasks")]
|
||||
[Authorize("Application")]
|
||||
[RequireFeature(FeatureFlagKeys.SecurityTasks)]
|
||||
public class SecurityTaskController : Controller
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IGetTaskDetailsForUserQuery _getTaskDetailsForUserQuery;
|
||||
|
||||
public SecurityTaskController(IUserService userService, IGetTaskDetailsForUserQuery getTaskDetailsForUserQuery)
|
||||
{
|
||||
_userService = userService;
|
||||
_getTaskDetailsForUserQuery = getTaskDetailsForUserQuery;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves security tasks for the current user.
|
||||
/// </summary>
|
||||
/// <param name="status">Optional filter for task status. If not provided returns tasks of all statuses.</param>
|
||||
/// <returns>A list response model containing the security tasks for the user.</returns>
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<SecurityTasksResponseModel>> Get([FromQuery] SecurityTaskStatus? status)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var securityTasks = await _getTaskDetailsForUserQuery.GetTaskDetailsForUserAsync(userId, status);
|
||||
var response = securityTasks.Select(x => new SecurityTasksResponseModel(x)).ToList();
|
||||
return new ListResponseModel<SecurityTasksResponseModel>(response);
|
||||
}
|
||||
}
|
30
src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs
Normal file
30
src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
|
||||
namespace Bit.Api.Vault.Models.Response;
|
||||
|
||||
public class SecurityTasksResponseModel : ResponseModel
|
||||
{
|
||||
public SecurityTasksResponseModel(SecurityTask securityTask, string obj = "securityTask")
|
||||
: base(obj)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(securityTask);
|
||||
|
||||
Id = securityTask.Id;
|
||||
OrganizationId = securityTask.OrganizationId;
|
||||
CipherId = securityTask.CipherId;
|
||||
Type = securityTask.Type;
|
||||
Status = securityTask.Status;
|
||||
CreationDate = securityTask.CreationDate;
|
||||
RevisionDate = securityTask.RevisionDate;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid? CipherId { get; set; }
|
||||
public SecurityTaskType Type { get; set; }
|
||||
public SecurityTaskStatus Status { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
13
src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs
Normal file
13
src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
|
||||
namespace Bit.Core.Vault.Queries;
|
||||
|
||||
public class GetTaskDetailsForUserQuery(ISecurityTaskRepository securityTaskRepository) : IGetTaskDetailsForUserQuery
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<SecurityTask>> GetTaskDetailsForUserAsync(Guid userId,
|
||||
SecurityTaskStatus? status = null)
|
||||
=> await securityTaskRepository.GetManyByUserIdStatusAsync(userId, status);
|
||||
}
|
15
src/Core/Vault/Queries/IGetTaskDetailsForUserQuery.cs
Normal file
15
src/Core/Vault/Queries/IGetTaskDetailsForUserQuery.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
|
||||
namespace Bit.Core.Vault.Queries;
|
||||
|
||||
public interface IGetTaskDetailsForUserQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves security tasks for a user based on their organization and cipher access permissions.
|
||||
/// </summary>
|
||||
/// <param name="userId">The Id of the user retrieving tasks</param>
|
||||
/// <param name="status">Optional filter for task status. If not provided, returns tasks of all statuses</param>
|
||||
/// <returns>A collection of security tasks</returns>
|
||||
Task<IEnumerable<SecurityTask>> GetTaskDetailsForUserAsync(Guid userId, SecurityTaskStatus? status = null);
|
||||
}
|
@ -1,9 +1,16 @@
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
|
||||
namespace Bit.Core.Vault.Repositories;
|
||||
|
||||
public interface ISecurityTaskRepository : IRepository<SecurityTask, Guid>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves security tasks for a user based on their organization and cipher access permissions.
|
||||
/// </summary>
|
||||
/// <param name="userId">The Id of the user retrieving tasks</param>
|
||||
/// <param name="status">Optional filter for task status. If not provided, returns tasks of all statuses</param>
|
||||
/// <returns></returns>
|
||||
Task<ICollection<SecurityTask>> GetManyByUserIdStatusAsync(Guid userId, SecurityTaskStatus? status = null);
|
||||
}
|
||||
|
@ -15,5 +15,6 @@ public static class VaultServiceCollectionExtensions
|
||||
private static void AddVaultQueries(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IOrganizationCiphersQuery, OrganizationCiphersQuery>();
|
||||
services.AddScoped<IGetTaskDetailsForUserQuery, GetTaskDetailsForUserQuery>();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
using Bit.Core.Settings;
|
||||
using System.Data;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Infrastructure.Dapper.Repositories;
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Bit.Infrastructure.Dapper.Vault.Repositories;
|
||||
|
||||
@ -15,4 +19,17 @@ public class SecurityTaskRepository : Repository<SecurityTask, Guid>, ISecurityT
|
||||
: base(connectionString, readOnlyConnectionString)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<SecurityTask>> GetManyByUserIdStatusAsync(Guid userId,
|
||||
SecurityTaskStatus? status = null)
|
||||
{
|
||||
await using var connection = new SqlConnection(ConnectionString);
|
||||
|
||||
var results = await connection.QueryAsync<SecurityTask>(
|
||||
$"[{Schema}].[SecurityTask_ReadByUserIdStatus]",
|
||||
new { UserId = userId, Status = status },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,90 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Vault.Repositories.Queries;
|
||||
|
||||
public class SecurityTaskReadByUserIdStatusQuery : IQuery<SecurityTask>
|
||||
{
|
||||
private readonly Guid _userId;
|
||||
private readonly SecurityTaskStatus? _status;
|
||||
|
||||
public SecurityTaskReadByUserIdStatusQuery(Guid userId, SecurityTaskStatus? status)
|
||||
{
|
||||
_userId = userId;
|
||||
_status = status;
|
||||
}
|
||||
|
||||
public IQueryable<SecurityTask> Run(DatabaseContext dbContext)
|
||||
{
|
||||
var query = from st in dbContext.SecurityTasks
|
||||
|
||||
join ou in dbContext.OrganizationUsers
|
||||
on st.OrganizationId equals ou.OrganizationId
|
||||
|
||||
join o in dbContext.Organizations
|
||||
on st.OrganizationId equals o.Id
|
||||
|
||||
join c in dbContext.Ciphers
|
||||
on st.CipherId equals c.Id into c_g
|
||||
from c in c_g.DefaultIfEmpty()
|
||||
|
||||
join cc in dbContext.CollectionCiphers
|
||||
on c.Id equals cc.CipherId into cc_g
|
||||
from cc in cc_g.DefaultIfEmpty()
|
||||
|
||||
join cu in dbContext.CollectionUsers
|
||||
on new { cc.CollectionId, OrganizationUserId = ou.Id } equals
|
||||
new { cu.CollectionId, cu.OrganizationUserId } into cu_g
|
||||
from cu in cu_g.DefaultIfEmpty()
|
||||
|
||||
join gu in dbContext.GroupUsers
|
||||
on new { CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals
|
||||
new { CollectionId = (Guid?)null, gu.OrganizationUserId } into gu_g
|
||||
from gu in gu_g.DefaultIfEmpty()
|
||||
|
||||
join cg in dbContext.CollectionGroups
|
||||
on new { cc.CollectionId, gu.GroupId } equals
|
||||
new { cg.CollectionId, cg.GroupId } into cg_g
|
||||
from cg in cg_g.DefaultIfEmpty()
|
||||
|
||||
where
|
||||
ou.UserId == _userId &&
|
||||
ou.Status == OrganizationUserStatusType.Confirmed &&
|
||||
o.Enabled &&
|
||||
(
|
||||
st.CipherId == null ||
|
||||
(
|
||||
c != null &&
|
||||
(
|
||||
(cu != null && !cu.ReadOnly) || (cg != null && !cg.ReadOnly && cu == null)
|
||||
)
|
||||
)
|
||||
) &&
|
||||
(_status == null || st.Status == _status)
|
||||
group st by new
|
||||
{
|
||||
st.Id,
|
||||
st.OrganizationId,
|
||||
st.CipherId,
|
||||
st.Type,
|
||||
st.Status,
|
||||
st.CreationDate,
|
||||
st.RevisionDate
|
||||
} into g
|
||||
select new SecurityTask
|
||||
{
|
||||
Id = g.Key.Id,
|
||||
OrganizationId = g.Key.OrganizationId,
|
||||
CipherId = g.Key.CipherId,
|
||||
Type = g.Key.Type,
|
||||
Status = g.Key.Status,
|
||||
CreationDate = g.Key.CreationDate,
|
||||
RevisionDate = g.Key.RevisionDate
|
||||
};
|
||||
|
||||
return query.OrderByDescending(st => st.CreationDate);
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Vault.Repositories.Queries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Vault.Repositories;
|
||||
@ -11,4 +14,15 @@ public class SecurityTaskRepository : Repository<Core.Vault.Entities.SecurityTas
|
||||
public SecurityTaskRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
|
||||
: base(serviceScopeFactory, mapper, (context) => context.SecurityTasks)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<Core.Vault.Entities.SecurityTask>> GetManyByUserIdStatusAsync(Guid userId,
|
||||
SecurityTaskStatus? status = null)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var query = new SecurityTaskReadByUserIdStatusQuery(userId, status);
|
||||
var data = await query.Run(dbContext).ToListAsync();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
CREATE PROCEDURE [dbo].[SecurityTask_ReadByUserIdStatus]
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Status TINYINT = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
ST.Id,
|
||||
ST.OrganizationId,
|
||||
ST.CipherId,
|
||||
ST.Type,
|
||||
ST.Status,
|
||||
ST.CreationDate,
|
||||
ST.RevisionDate
|
||||
FROM
|
||||
[dbo].[SecurityTaskView] ST
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUserView] OU ON OU.[OrganizationId] = ST.[OrganizationId]
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = ST.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[CipherView] C ON C.[Id] = ST.[CipherId]
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] AND C.[Id] IS NOT NULL
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] AND C.[Id] IS NOT NULL
|
||||
LEFT JOIN
|
||||
[dbo].[GroupUser] GU ON GU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] IS NULL AND C.[Id] IS NOT NULL
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = CC.[CollectionId]
|
||||
WHERE
|
||||
OU.[UserId] = @UserId
|
||||
AND OU.[Status] = 2 -- Ensure user is confirmed
|
||||
AND O.[Enabled] = 1
|
||||
AND (
|
||||
ST.[CipherId] IS NULL
|
||||
OR (
|
||||
C.[Id] IS NOT NULL
|
||||
AND (
|
||||
CU.[ReadOnly] = 0
|
||||
OR CG.[ReadOnly] = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
AND ST.[Status] = COALESCE(@Status, ST.[Status])
|
||||
GROUP BY
|
||||
ST.Id,
|
||||
ST.OrganizationId,
|
||||
ST.CipherId,
|
||||
ST.Type,
|
||||
ST.Status,
|
||||
ST.CreationDate,
|
||||
ST.RevisionDate
|
||||
ORDER BY ST.[CreationDate] DESC
|
||||
END
|
@ -0,0 +1,22 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Bit.Core.Vault.Entities;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Comparers;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the equality of two SecurityTask objects.
|
||||
/// </summary>
|
||||
public class SecurityTaskComparer : IEqualityComparer<SecurityTask>
|
||||
{
|
||||
public bool Equals(SecurityTask x, SecurityTask y)
|
||||
{
|
||||
return x.Id.Equals(y.Id) &&
|
||||
x.Type.Equals(y.Type) &&
|
||||
x.Status.Equals(y.Status);
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] SecurityTask obj)
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Infrastructure.IntegrationTest.Comparers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Vault.Repositories;
|
||||
@ -120,4 +124,103 @@ public class SecurityTaskRepositoryTests
|
||||
Assert.Equal(task.Id, updatedTask.Id);
|
||||
Assert.Equal(SecurityTaskStatus.Completed, updatedTask.Status);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task GetManyByUserIdAsync_ReturnsExpectedTasks(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
ISecurityTaskRepository securityTaskRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICollectionRepository collectionRepository)
|
||||
{
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Plan = "Test Plan",
|
||||
BillingEmail = "billing@email.com"
|
||||
});
|
||||
|
||||
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
});
|
||||
|
||||
var collection = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Name = "Test Collection 1",
|
||||
});
|
||||
|
||||
var collection2 = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Name = "Test Collection 2",
|
||||
});
|
||||
|
||||
var cipher1 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
|
||||
await cipherRepository.CreateAsync(cipher1, [collection.Id, collection2.Id]);
|
||||
|
||||
var cipher2 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
|
||||
await cipherRepository.CreateAsync(cipher2, [collection.Id]);
|
||||
|
||||
var task1 = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher1.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
var task2 = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher2.Id,
|
||||
Status = SecurityTaskStatus.Completed,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
var task3 = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher2.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
await collectionRepository.UpdateUsersAsync(collection.Id,
|
||||
new List<CollectionAccessSelection>
|
||||
{
|
||||
new() {Id = orgUser.Id, ReadOnly = false, HidePasswords = false, Manage = true}
|
||||
});
|
||||
|
||||
var allTasks = await securityTaskRepository.GetManyByUserIdStatusAsync(user.Id);
|
||||
Assert.Equal(3, allTasks.Count);
|
||||
Assert.Contains(task1, allTasks, new SecurityTaskComparer());
|
||||
Assert.Contains(task2, allTasks, new SecurityTaskComparer());
|
||||
Assert.Contains(task3, allTasks, new SecurityTaskComparer());
|
||||
|
||||
var pendingTasks = await securityTaskRepository.GetManyByUserIdStatusAsync(user.Id, SecurityTaskStatus.Pending);
|
||||
Assert.Equal(2, pendingTasks.Count);
|
||||
Assert.Contains(task1, pendingTasks, new SecurityTaskComparer());
|
||||
Assert.Contains(task3, pendingTasks, new SecurityTaskComparer());
|
||||
Assert.DoesNotContain(task2, pendingTasks, new SecurityTaskComparer());
|
||||
|
||||
var completedTasks = await securityTaskRepository.GetManyByUserIdStatusAsync(user.Id, SecurityTaskStatus.Completed);
|
||||
Assert.Single(completedTasks);
|
||||
Assert.Contains(task2, completedTasks, new SecurityTaskComparer());
|
||||
Assert.DoesNotContain(task1, completedTasks, new SecurityTaskComparer());
|
||||
Assert.DoesNotContain(task3, completedTasks, new SecurityTaskComparer());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
-- Security Task Read By UserId Status
|
||||
-- Stored Procedure: ReadByUserIdStatus
|
||||
CREATE OR ALTER PROCEDURE [dbo].[SecurityTask_ReadByUserIdStatus]
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Status TINYINT = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
ST.Id,
|
||||
ST.OrganizationId,
|
||||
ST.CipherId,
|
||||
ST.Type,
|
||||
ST.Status,
|
||||
ST.CreationDate,
|
||||
ST.RevisionDate
|
||||
FROM
|
||||
[dbo].[SecurityTaskView] ST
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUserView] OU ON OU.[OrganizationId] = ST.[OrganizationId]
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = ST.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[CipherView] C ON C.[Id] = ST.[CipherId]
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] AND C.[Id] IS NOT NULL
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] AND C.[Id] IS NOT NULL
|
||||
LEFT JOIN
|
||||
[dbo].[GroupUser] GU ON GU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] IS NULL AND C.[Id] IS NOT NULL
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = CC.[CollectionId]
|
||||
WHERE
|
||||
OU.[UserId] = @UserId
|
||||
AND OU.[Status] = 2 -- Ensure user is confirmed
|
||||
AND O.[Enabled] = 1
|
||||
AND (
|
||||
ST.[CipherId] IS NULL
|
||||
OR (
|
||||
C.[Id] IS NOT NULL
|
||||
AND (
|
||||
CU.[ReadOnly] = 0
|
||||
OR CG.[ReadOnly] = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
AND ST.[Status] = COALESCE(@Status, ST.[Status])
|
||||
GROUP BY
|
||||
ST.Id,
|
||||
ST.OrganizationId,
|
||||
ST.CipherId,
|
||||
ST.Type,
|
||||
ST.Status,
|
||||
ST.CreationDate,
|
||||
ST.RevisionDate
|
||||
ORDER BY ST.[CreationDate] DESC
|
||||
END
|
||||
GO
|
Loading…
Reference in New Issue
Block a user