Skip to content

Commit cdcdb5c

Browse files
committed
Merge branch 'main' into jmccannon/ac/pm-38927-org-user-role-validation
2 parents 016b992 + e2b5372 commit cdcdb5c

76 files changed

Lines changed: 13206 additions & 102 deletions

File tree

Some content is hidden

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

Directory.Build.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55

6-
<Version>2026.6.1</Version>
6+
<Version>2026.6.2</Version>
77

88
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
99
<ImplicitUsings>enable</ImplicitUsings>
1010
<IsTestProject Condition="'$(IsTestProject)' == '' and ($(MSBuildProjectName.EndsWith('.Test')) or $(MSBuildProjectName.EndsWith('.IntegrationTest')))">true</IsTestProject>
1111
<Nullable Condition="'$(Nullable)' == '' and '$(IsTestProject)' == 'true'">annotations</Nullable>
1212
<Nullable Condition="'$(Nullable)' == '' and '$(IsTestProject)' != 'true'">enable</Nullable>
1313
<TreatWarningsAsErrors Condition="'$(TreatWarningsAsErrors)' == ''">true</TreatWarningsAsErrors>
14+
15+
<WarningsNotAsErrors>$(WarningsNotAsErrors);BWA0001</WarningsNotAsErrors>
1416
<NuGetAuditLevel>critical</NuGetAuditLevel>
1517
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
1618
</PropertyGroup>

dev/secrets.json.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
2+
"seederSettings": {
3+
"username": "<your SeederApi username>",
4+
"password": "<your SeederApi password>"
5+
},
26
"adminSettings": {
37
"admins": "admin@localhost,owner@localhost,cs@localhost,billing@localhost,sales@localhost",
48
"role": {

src/Api/AdminConsole/Controllers/ValidationErrorTypedResultsExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Bit.Core.AdminConsole.Utilities.v2.Validation;
2+
using Bit.HttpExtensions;
23

34
namespace Microsoft.AspNetCore.Http.HttpResults;
45

src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ public class OrganizationUserUpdateRequestModel
102102
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
103103
public IEnumerable<Guid> Groups { get; set; }
104104

105+
#nullable enable
106+
[StrictEmailAddressNullable]
107+
[StringLength(256)]
108+
public string? Email { get; set; }
109+
110+
[StringLength(50)]
111+
public string? Name { get; set; }
112+
#nullable disable
113+
105114
public OrganizationUser ToOrganizationUser(OrganizationUser existingUser)
106115
{
107116
existingUser.Type = Type.Value;

src/Api/Models/Response/ListResponseModel.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55

66
namespace Bit.Api.Models.Response;
77

8+
/// <summary>
9+
/// A paginated list response wrapper.
10+
/// </summary>
11+
/// <remarks>
12+
/// Deprecated in favor of <c>Bit.HttpExtensions.ListResponseModel</c>.
13+
/// </remarks>
14+
[Obsolete(
15+
"Use Bit.HttpExtensions.ListResponseModel instead.",
16+
DiagnosticId = "BWA0001")]
817
public class ListResponseModel<T> : ResponseModel where T : ResponseModel
918
{
1019
public ListResponseModel(IEnumerable<T> data, string continuationToken = null)

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using Bit.Core.Repositories;
2020
using Bit.Core.Services;
2121
using Stripe;
22-
using Stripe.TestHelpers;
2322
using static Bit.Core.Billing.Constants.StripeConstants;
2423
using Event = Stripe.Event;
2524

@@ -322,10 +321,7 @@ private async Task SetSubscriptionToCancelAsync(Subscription subscription)
322321
{
323322
await _priceIncreaseScheduler.Release(subscription.CustomerId, subscription.Id);
324323

325-
if (subscription.TestClock != null)
326-
{
327-
await WaitForTestClockToAdvanceAsync(subscription.TestClock);
328-
}
324+
await _stripeAdapter.WaitForTestClockToAdvanceAsync(subscription.TestClock);
329325

330326
var now = subscription.TestClock?.FrozenTime ?? DateTime.UtcNow;
331327

@@ -442,19 +438,6 @@ private async Task RemovePasswordManagerCouponIfRemovingSecretsManagerTrialAsync
442438
}
443439
}
444440

445-
private async Task WaitForTestClockToAdvanceAsync(TestClock testClock)
446-
{
447-
while (testClock.Status != "ready")
448-
{
449-
await Task.Delay(TimeSpan.FromSeconds(2));
450-
testClock = await _stripeAdapter.GetTestClockAsync(testClock.Id);
451-
if (testClock.Status == "internal_failure")
452-
{
453-
throw new Exception("Stripe Test Clock encountered an internal failure");
454-
}
455-
}
456-
}
457-
458441
private async Task HandleScheduleTriggeredFamiliesMigrationAsync(
459442
Event parsedEvent,
460443
Subscription subscription,
@@ -646,6 +629,15 @@ private async Task HandleScheduleTriggeredBusinessMigrationAsync(
646629
}
647630

648631
organization.ChangePlan(targetPlan);
632+
633+
// Packaged sources (e.g. Teams Starter) store a flat bundle cap in Seats; reconcile to the billed per-seat quantity.
634+
if (sourcePlan.HasNonSeatBasedPasswordManagerPlan())
635+
{
636+
var billedSeatQuantity = subscription.Items
637+
.First(item => item.Price?.Id == targetPriceId).Quantity;
638+
organization.Seats = (int)Math.Max(1, billedSeatQuantity);
639+
}
640+
649641
await _organizationRepository.ReplaceAsync(organization);
650642

651643
var sourceProvidedServiceAccounts = sourcePlan.SecretsManager?.BaseServiceAccount ?? 0;
@@ -659,6 +651,8 @@ private async Task HandleScheduleTriggeredBusinessMigrationAsync(
659651
[MetadataKeys.MigrationGraceServiceAccounts] = grace.ToString(CultureInfo.InvariantCulture)
660652
};
661653

654+
await _stripeAdapter.WaitForTestClockToAdvanceAsync(subscription.TestClock);
655+
662656
try
663657
{
664658
await _stripeAdapter.UpdateSubscriptionAsync(subscription.Id,

src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
using Bit.Core.Repositories;
2424
using Bit.Core.Services;
2525
using Stripe;
26-
using Stripe.TestHelpers;
2726
using Event = Stripe.Event;
2827
using Plan = Bit.Core.Models.StaticStore.Plan;
2928
using PremiumPlan = Bit.Core.Billing.Pricing.Premium.Plan;
@@ -367,10 +366,7 @@ private async Task<bool> ScheduleBusinessPlanPriceMigrationAsync(
367366
return false;
368367
}
369368

370-
if (subscription.TestClock != null)
371-
{
372-
await WaitForTestClockToAdvanceAsync(subscription.TestClock);
373-
}
369+
await stripeAdapter.WaitForTestClockToAdvanceAsync(subscription.TestClock);
374370

375371
var migrationScheduled = await priceIncreaseScheduler.ScheduleForSubscription(subscription);
376372

@@ -667,19 +663,6 @@ async Task ResolveAndAddAsync(Coupon? expandedCoupon, string? couponId)
667663
return discounts;
668664
}
669665

670-
private async Task WaitForTestClockToAdvanceAsync(TestClock testClock)
671-
{
672-
while (testClock.Status != "ready")
673-
{
674-
await Task.Delay(TimeSpan.FromSeconds(2));
675-
testClock = await stripeAdapter.GetTestClockAsync(testClock.Id);
676-
if (testClock.Status == "internal_failure")
677-
{
678-
throw new Exception("Stripe Test Clock encountered an internal failure");
679-
}
680-
}
681-
}
682-
683666
#endregion
684667

685668
#region Premium Users

src/Core/Billing/Constants/StripeConstants.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ public static class TaxRegistrationStatus
197197
public const string Scheduled = "scheduled";
198198
}
199199

200+
public static class TestClockStatus
201+
{
202+
public const string Advancing = "advancing";
203+
public const string InternalFailure = "internal_failure";
204+
public const string Ready = "ready";
205+
}
206+
200207
public static class ValidateTaxLocationTiming
201208
{
202209
public const string Deferred = "deferred";

src/Core/Billing/Services/IStripeAdapter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,5 @@ Task<CustomerBalanceTransaction> CreateCustomerBalanceTransactionAsync(string cu
6363
Task<SubscriptionSchedule> UpdateSubscriptionScheduleAsync(string id, SubscriptionScheduleUpdateOptions options);
6464
Task<SubscriptionSchedule> ReleaseSubscriptionScheduleAsync(string id, SubscriptionScheduleReleaseOptions options = null);
6565
Task<TestClock> GetTestClockAsync(string testClockId, TestClockGetOptions options = null);
66+
Task WaitForTestClockToAdvanceAsync(TestClock testClock);
6667
}

src/Core/Billing/Services/Implementations/StripeAdapter.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Stripe;
77
using Stripe.Tax;
88
using Stripe.TestHelpers;
9+
using static Bit.Core.Billing.Constants.StripeConstants;
910
using BillingPortalSessionService = Stripe.BillingPortal.SessionService;
1011
using CheckoutSessionService = Stripe.Checkout.SessionService;
1112
using CustomerService = Stripe.CustomerService;
@@ -281,4 +282,22 @@ public Task<SubscriptionSchedule> ReleaseSubscriptionScheduleAsync(string id, Su
281282
******************/
282283
public Task<TestClock> GetTestClockAsync(string testClockId, TestClockGetOptions options = null) =>
283284
_testClockService.GetAsync(testClockId, options);
285+
286+
public async Task WaitForTestClockToAdvanceAsync(TestClock testClock)
287+
{
288+
if (testClock == null)
289+
{
290+
return;
291+
}
292+
293+
while (testClock.Status != TestClockStatus.Ready)
294+
{
295+
await Task.Delay(TimeSpan.FromSeconds(2));
296+
testClock = await _testClockService.GetAsync(testClock.Id);
297+
if (testClock.Status == TestClockStatus.InternalFailure)
298+
{
299+
throw new Exception("Stripe Test Clock encountered an internal failure");
300+
}
301+
}
302+
}
284303
}

0 commit comments

Comments
 (0)