Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into auth/pm-2996/add-auth-requ…
Browse files Browse the repository at this point in the history
…est-data-to-devices-response-model
  • Loading branch information
Patrick-Pimentel-Bitwarden committed Dec 19, 2024
2 parents 97fa55a + a3da5b2 commit 092b83c
Show file tree
Hide file tree
Showing 62 changed files with 2,467 additions and 107 deletions.
1 change: 1 addition & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"BitPay.Light",
"Braintree",
"coverlet.collector",
"CsvHelper",
"FluentAssertions",
"Kralizek.AutoFixture.Extensions.MockHttp",
"Microsoft.AspNetCore.Mvc.Testing",
Expand Down
1 change: 0 additions & 1 deletion bitwarden-server.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
TRADEMARK_GUIDELINES.md = TRADEMARK_GUIDELINES.md
SECURITY.md = SECURITY.md
NuGet.Config = NuGet.Config
LICENSE_FAQ.md = LICENSE_FAQ.md
LICENSE_BITWARDEN.txt = LICENSE_BITWARDEN.txt
LICENSE_AGPL.txt = LICENSE_AGPL.txt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
</div>
</div>
<div class="col-2">
<h3>Access Insights</h3>
<h3>Access Intelligence</h3>
<div class="form-check">
<input type="checkbox" class="form-check-input" asp-for="UseRiskInsights" disabled='@(canEditPlan ? null : "disabled")'>
<label class="form-check-label" asp-for="UseRiskInsights"></label>
Expand Down
2 changes: 2 additions & 0 deletions src/Admin/Utilities/RolePermissionMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public static class RolePermissionMapping
Permission.User_Billing_LaunchGateway,
Permission.Org_List_View,
Permission.Org_OrgInformation_View,
Permission.Org_Delete,
Permission.Org_GeneralDetails_View,
Permission.Org_BusinessInformation_View,
Permission.Org_BillingInformation_View,
Expand Down Expand Up @@ -156,6 +157,7 @@ public static class RolePermissionMapping
Permission.Org_Billing_View,
Permission.Org_Billing_Edit,
Permission.Org_Billing_LaunchGateway,
Permission.Org_Delete,
Permission.Provider_Edit,
Permission.Provider_View,
Permission.Provider_List_View,
Expand Down
8 changes: 8 additions & 0 deletions src/Api/Auth/Controllers/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,14 @@ public async Task VerifyOTP([FromBody] VerifyOTPRequestModel model)
}
}

[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
[AllowAnonymous]
[HttpPost("resend-new-device-otp")]
public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificatioRequestModel request)
{
await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret);
}

private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
{
var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;

namespace Bit.Api.Auth.Models.Request.Accounts;

public class UnauthenticatedSecretVerificatioRequestModel : SecretVerificationRequestModel
{
[Required]
[StrictEmailAddress]
[StringLength(256)]
public string Email { get; set; }
}
44 changes: 44 additions & 0 deletions src/Api/Billing/Public/Controllers/OrganizationController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using Bit.Api.Billing.Public.Models;
using Bit.Api.Models.Public.Response;
using Bit.Core.Context;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
Expand Down Expand Up @@ -35,6 +36,49 @@ public OrganizationController(
_logger = logger;
}

/// <summary>
/// Retrieves the subscription details for the current organization.
/// </summary>
/// <returns>
/// Returns an object containing the subscription details if successful.
/// </returns>
[HttpGet("subscription")]
[SelfHosted(NotSelfHostedOnly = true)]
[ProducesResponseType(typeof(OrganizationSubscriptionDetailsResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetSubscriptionAsync()
{
try
{
var organizationId = _currentContext.OrganizationId.Value;
var organization = await _organizationRepository.GetByIdAsync(organizationId);

var subscriptionDetails = new OrganizationSubscriptionDetailsResponseModel
{
PasswordManager = new PasswordManagerSubscriptionDetails
{
Seats = organization.Seats,
MaxAutoScaleSeats = organization.MaxAutoscaleSeats,
Storage = organization.MaxStorageGb
},
SecretsManager = new SecretsManagerSubscriptionDetails
{
Seats = organization.SmSeats,
MaxAutoScaleSeats = organization.MaxAutoscaleSmSeats,
ServiceAccounts = organization.SmServiceAccounts,
MaxAutoScaleServiceAccounts = organization.MaxAutoscaleSmServiceAccounts
}
};

return Ok(subscriptionDetails);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled error while retrieving the subscription details");
return StatusCode(500, new { Message = "An error occurred while retrieving the subscription details." });
}
}

/// <summary>
/// Update the organization's current subscription for Password Manager and/or Secrets Manager.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;

namespace Bit.Api.Billing.Public.Models;

public class OrganizationSubscriptionDetailsResponseModel : IValidatableObject
{
public PasswordManagerSubscriptionDetails PasswordManager { get; set; }
public SecretsManagerSubscriptionDetails SecretsManager { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (PasswordManager == null && SecretsManager == null)
{
yield return new ValidationResult("At least one of PasswordManager or SecretsManager must be provided.");
}

yield return ValidationResult.Success;
}
}
public class PasswordManagerSubscriptionDetails
{
public int? Seats { get; set; }
public int? MaxAutoScaleSeats { get; set; }
public short? Storage { get; set; }
}

public class SecretsManagerSubscriptionDetails
{
public int? Seats { get; set; }
public int? MaxAutoScaleSeats { get; set; }
public int? ServiceAccounts { get; set; }
public int? MaxAutoScaleServiceAccounts { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#nullable enable
using Bit.Api.KeyManagement.Models.Requests;
using Bit.Core;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Commands.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Bit.Api.KeyManagement.Controllers;

[Route("accounts/key-management")]
[Authorize("Application")]
public class AccountsKeyManagementController : Controller
{
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
private readonly IFeatureService _featureService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IRegenerateUserAsymmetricKeysCommand _regenerateUserAsymmetricKeysCommand;
private readonly IUserService _userService;

public AccountsKeyManagementController(IUserService userService,
IFeatureService featureService,
IOrganizationUserRepository organizationUserRepository,
IEmergencyAccessRepository emergencyAccessRepository,
IRegenerateUserAsymmetricKeysCommand regenerateUserAsymmetricKeysCommand)
{
_userService = userService;
_featureService = featureService;
_regenerateUserAsymmetricKeysCommand = regenerateUserAsymmetricKeysCommand;
_organizationUserRepository = organizationUserRepository;
_emergencyAccessRepository = emergencyAccessRepository;
}

[HttpPost("regenerate-keys")]
public async Task RegenerateKeysAsync([FromBody] KeyRegenerationRequestModel request)
{
if (!_featureService.IsEnabled(FeatureFlagKeys.PrivateKeyRegeneration))
{
throw new NotFoundException();
}

var user = await _userService.GetUserByPrincipalAsync(User) ?? throw new UnauthorizedAccessException();
var usersOrganizationAccounts = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var designatedEmergencyAccess = await _emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(user.Id);
await _regenerateUserAsymmetricKeysCommand.RegenerateKeysAsync(request.ToUserAsymmetricKeys(user.Id),
usersOrganizationAccounts, designatedEmergencyAccess);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#nullable enable
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.Utilities;

namespace Bit.Api.KeyManagement.Models.Requests;

public class KeyRegenerationRequestModel
{
public required string UserPublicKey { get; set; }

[EncryptedString]
public required string UserKeyEncryptedUserPrivateKey { get; set; }

public UserAsymmetricKeys ToUserAsymmetricKeys(Guid userId)
{
return new UserAsymmetricKeys
{
UserId = userId,
PublicKey = UserPublicKey,
UserKeyEncryptedPrivateKey = UserKeyEncryptedUserPrivateKey,
};
}
}
71 changes: 71 additions & 0 deletions src/Api/NotificationCenter/Controllers/NotificationsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#nullable enable
using Bit.Api.Models.Response;
using Bit.Api.NotificationCenter.Models.Request;
using Bit.Api.NotificationCenter.Models.Response;
using Bit.Core.Models.Data;
using Bit.Core.NotificationCenter.Commands.Interfaces;
using Bit.Core.NotificationCenter.Models.Filter;
using Bit.Core.NotificationCenter.Queries.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Bit.Api.NotificationCenter.Controllers;

[Route("notifications")]
[Authorize("Application")]
public class NotificationsController : Controller
{
private readonly IGetNotificationStatusDetailsForUserQuery _getNotificationStatusDetailsForUserQuery;
private readonly IMarkNotificationDeletedCommand _markNotificationDeletedCommand;
private readonly IMarkNotificationReadCommand _markNotificationReadCommand;

public NotificationsController(
IGetNotificationStatusDetailsForUserQuery getNotificationStatusDetailsForUserQuery,
IMarkNotificationDeletedCommand markNotificationDeletedCommand,
IMarkNotificationReadCommand markNotificationReadCommand)
{
_getNotificationStatusDetailsForUserQuery = getNotificationStatusDetailsForUserQuery;
_markNotificationDeletedCommand = markNotificationDeletedCommand;
_markNotificationReadCommand = markNotificationReadCommand;
}

[HttpGet("")]
public async Task<ListResponseModel<NotificationResponseModel>> ListAsync(
[FromQuery] NotificationFilterRequestModel filter)
{
var pageOptions = new PageOptions
{
ContinuationToken = filter.ContinuationToken,
PageSize = filter.PageSize
};

var notificationStatusFilter = new NotificationStatusFilter
{
Read = filter.ReadStatusFilter,
Deleted = filter.DeletedStatusFilter
};

var notificationStatusDetailsPagedResult =
await _getNotificationStatusDetailsForUserQuery.GetByUserIdStatusFilterAsync(notificationStatusFilter,
pageOptions);

var responses = notificationStatusDetailsPagedResult.Data
.Select(n => new NotificationResponseModel(n))
.ToList();

return new ListResponseModel<NotificationResponseModel>(responses,
notificationStatusDetailsPagedResult.ContinuationToken);
}

[HttpPatch("{id}/delete")]
public async Task MarkAsDeletedAsync([FromRoute] Guid id)
{
await _markNotificationDeletedCommand.MarkDeletedAsync(id);
}

[HttpPatch("{id}/read")]
public async Task MarkAsReadAsync([FromRoute] Guid id)
{
await _markNotificationReadCommand.MarkReadAsync(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#nullable enable
using System.ComponentModel.DataAnnotations;

namespace Bit.Api.NotificationCenter.Models.Request;

public class NotificationFilterRequestModel : IValidatableObject
{
/// <summary>
/// Filters notifications by read status. When not set, includes notifications without a status.
/// </summary>
public bool? ReadStatusFilter { get; set; }

/// <summary>
/// Filters notifications by deleted status. When not set, includes notifications without a status.
/// </summary>
public bool? DeletedStatusFilter { get; set; }

/// <summary>
/// A cursor for use in pagination.
/// </summary>
[StringLength(9)]
public string? ContinuationToken { get; set; }

/// <summary>
/// The number of items to return in a single page.
/// Default 10. Minimum 10, maximum 1000.
/// </summary>
[Range(10, 1000)]
public int PageSize { get; set; } = 10;

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(ContinuationToken) &&
(!int.TryParse(ContinuationToken, out var pageNumber) || pageNumber <= 0))
{
yield return new ValidationResult(
"Continuation token must be a positive, non zero integer.",
[nameof(ContinuationToken)]);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#nullable enable
using Bit.Core.Models.Api;
using Bit.Core.NotificationCenter.Enums;
using Bit.Core.NotificationCenter.Models.Data;

namespace Bit.Api.NotificationCenter.Models.Response;

public class NotificationResponseModel : ResponseModel
{
private const string _objectName = "notification";

public NotificationResponseModel(NotificationStatusDetails notificationStatusDetails, string obj = _objectName)
: base(obj)
{
if (notificationStatusDetails == null)
{
throw new ArgumentNullException(nameof(notificationStatusDetails));
}

Id = notificationStatusDetails.Id;
Priority = notificationStatusDetails.Priority;
Title = notificationStatusDetails.Title;
Body = notificationStatusDetails.Body;
Date = notificationStatusDetails.RevisionDate;
ReadDate = notificationStatusDetails.ReadDate;
DeletedDate = notificationStatusDetails.DeletedDate;
}

public NotificationResponseModel() : base(_objectName)
{
}

public Guid Id { get; set; }

public Priority Priority { get; set; }

public string? Title { get; set; }

public string? Body { get; set; }

public DateTime Date { get; set; }

public DateTime? ReadDate { get; set; }

public DateTime? DeletedDate { get; set; }
}
Loading

0 comments on commit 092b83c

Please sign in to comment.