Skip to content

Commit

Permalink
Merge branch 'main' into renovate/serilog.extensions.logging-9.x
Browse files Browse the repository at this point in the history
  • Loading branch information
cyprain-okeke authored Dec 13, 2024
2 parents d46ddce + 6d9c8d0 commit 8678402
Show file tree
Hide file tree
Showing 20 changed files with 992 additions and 455 deletions.
476 changes: 256 additions & 220 deletions bitwarden_license/src/Sso/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions bitwarden_license/src/Sso/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"devDependencies": {
"css-loader": "7.1.2",
"expose-loader": "5.0.0",
"mini-css-extract-plugin": "2.9.1",
"mini-css-extract-plugin": "2.9.2",
"sass": "1.79.5",
"sass-loader": "16.0.2",
"webpack": "5.95.0",
"sass-loader": "16.0.4",
"webpack": "5.97.1",
"webpack-cli": "5.1.4"
}
}
476 changes: 256 additions & 220 deletions src/Admin/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/Admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"devDependencies": {
"css-loader": "7.1.2",
"expose-loader": "5.0.0",
"mini-css-extract-plugin": "2.9.1",
"mini-css-extract-plugin": "2.9.2",
"sass": "1.79.5",
"sass-loader": "16.0.2",
"webpack": "5.95.0",
"sass-loader": "16.0.4",
"webpack": "5.97.1",
"webpack-cli": "5.1.4"
}
}
40 changes: 40 additions & 0 deletions src/Api/Vault/Controllers/SecurityTaskController.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 30 additions & 0 deletions src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
<td style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-style: normal; font-weight: 400; font-size: 16px; line-height: 24px; margin-top: 30px; margin-bottom: 25px; margin-left: 35px; margin-right: 35px;">
<b>Here's what that means:</b>
<ul>
<li>This account should only be used to store items related to {{OrganizationName}}</li>
<li>Admins managing your Bitwarden organization manage your email address and other account settings</li>
<li>Admins can also revoke or delete your account at any time</li>
<li>Your administrators can delete your account at any time</li>
<li>You cannot leave the organization</li>
</ul>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
As a member of {{OrganizationName}}, your Bitwarden account is claimed and owned by your organization.

Here's what that means:
- This account should only be used to store items related to {{OrganizationName}}
- Your admins managing your Bitwarden organization manages your email address and other account settings
- Your admins can also revoke or delete your account at any time
- Your administrators can delete your account at any time
- You cannot leave the organization

For more information, please refer to the following help article: Claimed Accounts (https://bitwarden.com/help/claimed-accounts)
2 changes: 1 addition & 1 deletion src/Core/Services/Implementations/HandlebarsMailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ MailQueueMessage CreateMessage(string emailAddress, Organization org) =>
"AdminConsole.DomainClaimedByOrganization",
new ClaimedDomainUserNotificationViewModel
{
TitleFirst = $"Hey {emailAddress}, here is a heads up on your claimed account:",
TitleFirst = $"Hey {emailAddress}, your account is owned by {org.DisplayName()}",
OrganizationName = CoreHelpers.SanitizeForEmail(org.DisplayName(), false)
});
}
Expand Down
13 changes: 13 additions & 0 deletions src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions src/Core/Vault/Queries/IGetTaskDetailsForUserQuery.cs
Original file line number Diff line number Diff line change
@@ -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);
}
9 changes: 8 additions & 1 deletion src/Core/Vault/Repositories/ISecurityTaskRepository.cs
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions src/Core/Vault/VaultServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ public static IServiceCollection AddVaultServices(this IServiceCollection servic
private static void AddVaultQueries(this IServiceCollection services)
{
services.AddScoped<IOrganizationCiphersQuery, OrganizationCiphersQuery>();
services.AddScoped<IGetTaskDetailsForUserQuery, GetTaskDetailsForUserQuery>();
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,4 +19,17 @@ public SecurityTaskRepository(string connectionString, string readOnlyConnection
: 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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 8678402

Please sign in to comment.