Skip to content

Commit ce7ac0e

Browse files
Merge branch 'main' into ac/pm-22434/remove-create-default-location-ff-refs
2 parents af24307 + 2442d2d commit ce7ac0e

File tree

59 files changed

+23650
-226
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+23650
-226
lines changed

.github/workflows/review-code.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Code Review
22

33
on:
44
pull_request:
5-
types: [opened, labeled]
5+
types: [opened, synchronize, reopened]
66

77
permissions: {}
88

bitwarden_license/src/Sso/Controllers/AccountController.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -680,22 +680,10 @@ await _organizationService.AdjustSeatsAsync(organization.Id,
680680
ApiKey = CoreHelpers.SecureRandomString(30)
681681
};
682682

683-
/*
684-
The feature flag is checked here so that we can send the new MJML welcome email templates.
685-
The other organization invites flows have an OrganizationUser allowing the RegisterUserCommand the ability
686-
to fetch the Organization. The old method RegisterUser(User) here does not have that context, so we need
687-
to use a new method RegisterSSOAutoProvisionedUserAsync(User, Organization) to send the correct email.
688-
[PM-28057]: Prefer RegisterSSOAutoProvisionedUserAsync for SSO auto-provisioned users.
689-
TODO: Remove Feature flag: PM-28221
690-
*/
691-
if (_featureService.IsEnabled(FeatureFlagKeys.MjmlWelcomeEmailTemplates))
692-
{
693-
await _registerUserCommand.RegisterSSOAutoProvisionedUserAsync(newUser, organization);
694-
}
695-
else
696-
{
697-
await _registerUserCommand.RegisterUser(newUser);
698-
}
683+
// Always use RegisterSSOAutoProvisionedUserAsync to ensure organization context is available
684+
// for domain validation (BlockClaimedDomainAccountCreation policy) and welcome emails.
685+
// The feature flag logic for welcome email templates is handled internally by RegisterUserCommand.
686+
await _registerUserCommand.RegisterSSOAutoProvisionedUserAsync(newUser, organization);
699687

700688
// If the organization has 2fa policy enabled, make sure to default jit user 2fa to email
701689
var twoFactorPolicy =

bitwarden_license/test/SSO.Test/Controllers/AccountControllerTest.cs

Lines changed: 0 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using Bit.Core.Auth.Models.Business.Tokenables;
77
using Bit.Core.Auth.Models.Data;
88
using Bit.Core.Auth.Repositories;
9-
using Bit.Core.Auth.UserFeatures.Registration;
109
using Bit.Core.Entities;
1110
using Bit.Core.Enums;
1211
using Bit.Core.Repositories;
@@ -21,7 +20,6 @@
2120
using Duende.IdentityServer.Services;
2221
using Microsoft.AspNetCore.Authentication;
2322
using Microsoft.AspNetCore.Http;
24-
using Microsoft.AspNetCore.Identity;
2523
using Microsoft.AspNetCore.Mvc;
2624
using Microsoft.Extensions.DependencyInjection;
2725
using NSubstitute;
@@ -1013,133 +1011,6 @@ public async Task ExternalCallback_Measurements_FlagOnVsOff_Comparisons(
10131011
}
10141012
}
10151013

1016-
[Theory, BitAutoData]
1017-
public async Task AutoProvisionUserAsync_WithFeatureFlagEnabled_CallsRegisterSSOAutoProvisionedUser(
1018-
SutProvider<AccountController> sutProvider)
1019-
{
1020-
// Arrange
1021-
var orgId = Guid.NewGuid();
1022-
var providerUserId = "ext-new-user";
1023-
var email = "newuser@example.com";
1024-
var organization = new Organization { Id = orgId, Name = "Test Org", Seats = null };
1025-
1026-
// No existing user (JIT provisioning scenario)
1027-
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(email).Returns((User?)null);
1028-
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(organization);
1029-
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationEmailAsync(orgId, email)
1030-
.Returns((OrganizationUser?)null);
1031-
1032-
// Feature flag enabled
1033-
sutProvider.GetDependency<IFeatureService>()
1034-
.IsEnabled(FeatureFlagKeys.MjmlWelcomeEmailTemplates)
1035-
.Returns(true);
1036-
1037-
// Mock the RegisterSSOAutoProvisionedUserAsync to return success
1038-
sutProvider.GetDependency<IRegisterUserCommand>()
1039-
.RegisterSSOAutoProvisionedUserAsync(Arg.Any<User>(), Arg.Any<Organization>())
1040-
.Returns(IdentityResult.Success);
1041-
1042-
var claims = new[]
1043-
{
1044-
new Claim(JwtClaimTypes.Email, email),
1045-
new Claim(JwtClaimTypes.Name, "New User")
1046-
} as IEnumerable<Claim>;
1047-
var config = new SsoConfigurationData();
1048-
1049-
var method = typeof(AccountController).GetMethod(
1050-
"CreateUserAndOrgUserConditionallyAsync",
1051-
BindingFlags.Instance | BindingFlags.NonPublic);
1052-
Assert.NotNull(method);
1053-
1054-
// Act
1055-
var task = (Task<(User user, Organization organization, OrganizationUser orgUser)>)method!.Invoke(
1056-
sutProvider.Sut,
1057-
new object[]
1058-
{
1059-
orgId.ToString(),
1060-
providerUserId,
1061-
claims,
1062-
null!,
1063-
config
1064-
})!;
1065-
1066-
var result = await task;
1067-
1068-
// Assert
1069-
await sutProvider.GetDependency<IRegisterUserCommand>().Received(1)
1070-
.RegisterSSOAutoProvisionedUserAsync(
1071-
Arg.Is<User>(u => u.Email == email && u.Name == "New User"),
1072-
Arg.Is<Organization>(o => o.Id == orgId && o.Name == "Test Org"));
1073-
1074-
Assert.NotNull(result.user);
1075-
Assert.Equal(email, result.user.Email);
1076-
Assert.Equal(organization.Id, result.organization.Id);
1077-
}
1078-
1079-
[Theory, BitAutoData]
1080-
public async Task AutoProvisionUserAsync_WithFeatureFlagDisabled_CallsRegisterUserInstead(
1081-
SutProvider<AccountController> sutProvider)
1082-
{
1083-
// Arrange
1084-
var orgId = Guid.NewGuid();
1085-
var providerUserId = "ext-legacy-user";
1086-
var email = "legacyuser@example.com";
1087-
var organization = new Organization { Id = orgId, Name = "Test Org", Seats = null };
1088-
1089-
// No existing user (JIT provisioning scenario)
1090-
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(email).Returns((User?)null);
1091-
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(orgId).Returns(organization);
1092-
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationEmailAsync(orgId, email)
1093-
.Returns((OrganizationUser?)null);
1094-
1095-
// Feature flag disabled
1096-
sutProvider.GetDependency<IFeatureService>()
1097-
.IsEnabled(FeatureFlagKeys.MjmlWelcomeEmailTemplates)
1098-
.Returns(false);
1099-
1100-
// Mock the RegisterUser to return success
1101-
sutProvider.GetDependency<IRegisterUserCommand>()
1102-
.RegisterUser(Arg.Any<User>())
1103-
.Returns(IdentityResult.Success);
1104-
1105-
var claims = new[]
1106-
{
1107-
new Claim(JwtClaimTypes.Email, email),
1108-
new Claim(JwtClaimTypes.Name, "Legacy User")
1109-
} as IEnumerable<Claim>;
1110-
var config = new SsoConfigurationData();
1111-
1112-
var method = typeof(AccountController).GetMethod(
1113-
"CreateUserAndOrgUserConditionallyAsync",
1114-
BindingFlags.Instance | BindingFlags.NonPublic);
1115-
Assert.NotNull(method);
1116-
1117-
// Act
1118-
var task = (Task<(User user, Organization organization, OrganizationUser orgUser)>)method!.Invoke(
1119-
sutProvider.Sut,
1120-
new object[]
1121-
{
1122-
orgId.ToString(),
1123-
providerUserId,
1124-
claims,
1125-
null!,
1126-
config
1127-
})!;
1128-
1129-
var result = await task;
1130-
1131-
// Assert
1132-
await sutProvider.GetDependency<IRegisterUserCommand>().Received(1)
1133-
.RegisterUser(Arg.Is<User>(u => u.Email == email && u.Name == "Legacy User"));
1134-
1135-
// Verify the new method was NOT called
1136-
await sutProvider.GetDependency<IRegisterUserCommand>().DidNotReceive()
1137-
.RegisterSSOAutoProvisionedUserAsync(Arg.Any<User>(), Arg.Any<Organization>());
1138-
1139-
Assert.NotNull(result.user);
1140-
Assert.Equal(email, result.user.Email);
1141-
}
1142-
11431014
[Theory, BitAutoData]
11441015
public void ExternalChallenge_WithMatchingOrgId_Succeeds(
11451016
SutProvider<AccountController> sutProvider,

src/Api/Billing/Controllers/VNext/AccountBillingVNextController.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Bit.Api.Billing.Attributes;
22
using Bit.Api.Billing.Models.Requests.Payment;
33
using Bit.Api.Billing.Models.Requests.Premium;
4+
using Bit.Api.Billing.Models.Requests.Storage;
45
using Bit.Core;
6+
using Bit.Core.Billing.Licenses.Queries;
57
using Bit.Core.Billing.Payment.Commands;
68
using Bit.Core.Billing.Payment.Queries;
79
using Bit.Core.Billing.Premium.Commands;
@@ -21,7 +23,9 @@ public class AccountBillingVNextController(
2123
ICreatePremiumCloudHostedSubscriptionCommand createPremiumCloudHostedSubscriptionCommand,
2224
IGetCreditQuery getCreditQuery,
2325
IGetPaymentMethodQuery getPaymentMethodQuery,
24-
IUpdatePaymentMethodCommand updatePaymentMethodCommand) : BaseBillingController
26+
IGetUserLicenseQuery getUserLicenseQuery,
27+
IUpdatePaymentMethodCommand updatePaymentMethodCommand,
28+
IUpdatePremiumStorageCommand updatePremiumStorageCommand) : BaseBillingController
2529
{
2630
[HttpGet("credit")]
2731
[InjectUser]
@@ -77,4 +81,24 @@ public async Task<IResult> CreateSubscriptionAsync(
7781
user, paymentMethod, billingAddress, additionalStorageGb);
7882
return Handle(result);
7983
}
84+
85+
[HttpGet("license")]
86+
[InjectUser]
87+
public async Task<IResult> GetLicenseAsync(
88+
[BindNever] User user)
89+
{
90+
var response = await getUserLicenseQuery.Run(user);
91+
return TypedResults.Ok(response);
92+
}
93+
94+
[HttpPut("storage")]
95+
[RequireFeature(FeatureFlagKeys.PM29594_UpdateIndividualSubscriptionPage)]
96+
[InjectUser]
97+
public async Task<IResult> UpdateStorageAsync(
98+
[BindNever] User user,
99+
[FromBody] StorageUpdateRequest request)
100+
{
101+
var result = await updatePremiumStorageCommand.Run(user, request.AdditionalStorageGb);
102+
return Handle(result);
103+
}
80104
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace Bit.Api.Billing.Models.Requests.Storage;
4+
5+
/// <summary>
6+
/// Request model for updating storage allocation on a user's premium subscription.
7+
/// Allows for both increasing and decreasing storage in an idempotent manner.
8+
/// </summary>
9+
public class StorageUpdateRequest : IValidatableObject
10+
{
11+
/// <summary>
12+
/// The additional storage in GB beyond the base storage.
13+
/// Must be between 0 and the maximum allowed (minus base storage).
14+
/// </summary>
15+
[Required]
16+
[Range(0, 99)]
17+
public short AdditionalStorageGb { get; set; }
18+
19+
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
20+
{
21+
if (AdditionalStorageGb < 0)
22+
{
23+
yield return new ValidationResult(
24+
"Additional storage cannot be negative.",
25+
new[] { nameof(AdditionalStorageGb) });
26+
}
27+
28+
if (AdditionalStorageGb > 99)
29+
{
30+
yield return new ValidationResult(
31+
"Maximum additional storage is 99 GB.",
32+
new[] { nameof(AdditionalStorageGb) });
33+
}
34+
}
35+
}

src/Api/KeyManagement/Validators/SendRotationValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public async Task<IReadOnlyList<Send>> ValidateAsync(User user, IEnumerable<Send
4444
throw new BadRequestException("All existing sends must be included in the rotation.");
4545
}
4646

47-
result.Add(send.ToSend(existing, _sendAuthorizationService));
47+
result.Add(send.UpdateSend(existing, _sendAuthorizationService));
4848
}
4949

5050
return result;

0 commit comments

Comments
 (0)