Skip to content

Commit

Permalink
Merge branch 'master' into sm/sm-863
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas-Avery authored Jul 25, 2023
2 parents 2c037b6 + 764468a commit f580f96
Show file tree
Hide file tree
Showing 51 changed files with 2,872 additions and 373 deletions.
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>2023.7.0</Version>
<Version>2023.7.1</Version>
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down Expand Up @@ -63,4 +63,4 @@
</ItemGroup>
</Target>

</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ public async Task DeleteManyByIdAsync(IEnumerable<Guid> ids)
return policy == null ? (false, false) : (policy.Read, policy.Write);
}

public async Task<int> GetServiceAccountCountByOrganizationIdAsync(Guid organizationId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
return await dbContext.ServiceAccount
.CountAsync(ou => ou.OrganizationId == organizationId);
}
}

private static Expression<Func<ServiceAccount, bool>> UserHasReadAccessToServiceAccount(Guid userId) => sa =>
sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read));
Expand Down
37 changes: 34 additions & 3 deletions src/Api/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
Expand Down Expand Up @@ -50,6 +51,8 @@ public class OrganizationsController : Controller
private readonly IFeatureService _featureService;
private readonly GlobalSettings _globalSettings;
private readonly ILicensingService _licensingService;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IUpgradeOrganizationPlanCommand _upgradeOrganizationPlanCommand;

public OrganizationsController(
IOrganizationRepository organizationRepository,
Expand All @@ -70,7 +73,9 @@ public OrganizationsController(
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IFeatureService featureService,
GlobalSettings globalSettings,
ILicensingService licensingService)
ILicensingService licensingService,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
Expand All @@ -91,6 +96,8 @@ public OrganizationsController(
_featureService = featureService;
_globalSettings = globalSettings;
_licensingService = licensingService;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_upgradeOrganizationPlanCommand = upgradeOrganizationPlanCommand;
}

[HttpGet("{id}")]
Expand Down Expand Up @@ -306,7 +313,7 @@ public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody] Organi
throw new NotFoundException();
}

var result = await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
var result = await _upgradeOrganizationPlanCommand.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 };
}

Expand All @@ -319,10 +326,34 @@ public async Task PostSubscription(string id, [FromBody] OrganizationSubscriptio
{
throw new NotFoundException();
}

await _organizationService.UpdateSubscription(orgIdGuid, model.SeatAdjustment, model.MaxAutoscaleSeats);
}

[HttpPost("{id}/sm-subscription")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostSmSubscription(Guid id, [FromBody] SecretsManagerSubscriptionUpdateRequestModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}

if (!await _currentContext.EditSubscription(id))
{
throw new NotFoundException();
}

var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
if (secretsManagerPlan == null)
{
throw new NotFoundException("Invalid Secrets Manager plan.");
}

var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization, secretsManagerPlan);
await _updateSecretsManagerSubscriptionCommand.UpdateSecretsManagerSubscription(organizationUpdate);
}

[HttpPost("{id}/seat")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody] OrganizationSeatRequestModel model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class OrganizationCreateRequestModel : IValidatableObject
[StringLength(2)]
public string BillingAddressCountry { get; set; }
public int? MaxAutoscaleSeats { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalSmSeats { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalServiceAccounts { get; set; }
[Required]
public bool UseSecretsManager { get; set; }

public virtual OrganizationSignup ToOrganizationSignup(User user)
{
Expand All @@ -58,6 +64,9 @@ public virtual OrganizationSignup ToOrganizationSignup(User user)
BillingEmail = BillingEmail,
BusinessName = BusinessName,
CollectionName = CollectionName,
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(),
AdditionalServiceAccounts = AdditionalServiceAccounts.GetValueOrDefault(),
UseSecretsManager = UseSecretsManager,
TaxInfo = new TaxInfo
{
TaxIdNumber = TaxIdNumber,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ public class OrganizationUpgradeRequestModel
public int AdditionalSeats { get; set; }
[Range(0, 99)]
public short? AdditionalStorageGb { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalSmSeats { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalServiceAccounts { get; set; }
[Required]
public bool UseSecretsManager { get; set; }
public bool PremiumAccessAddon { get; set; }
public string BillingAddressCountry { get; set; }
public string BillingAddressPostalCode { get; set; }
Expand All @@ -24,6 +30,9 @@ public OrganizationUpgrade ToOrganizationUpgrade()
{
AdditionalSeats = AdditionalSeats,
AdditionalStorageGb = AdditionalStorageGb.GetValueOrDefault(),
AdditionalServiceAccounts = AdditionalServiceAccounts.GetValueOrDefault(0),
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(0),
UseSecretsManager = UseSecretsManager,
BusinessName = BusinessName,
Plan = PlanType,
PremiumAccessAddon = PremiumAccessAddon,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Models.Business;
using Bit.Core.Models.StaticStore;

namespace Bit.Api.Models.Request.Organizations;

public class SecretsManagerSubscriptionUpdateRequestModel
{
[Required]
public int SeatAdjustment { get; set; }
public int? MaxAutoscaleSeats { get; set; }
public int ServiceAccountAdjustment { get; set; }
public int? MaxAutoscaleServiceAccounts { get; set; }

public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization, Plan plan)
{
var newTotalSeats = organization.SmSeats.GetValueOrDefault() + SeatAdjustment;
var newTotalServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + ServiceAccountAdjustment;

var orgUpdate = new SecretsManagerSubscriptionUpdate
{
OrganizationId = organization.Id,

SmSeatsAdjustment = SeatAdjustment,
SmSeats = newTotalSeats,
SmSeatsExcludingBase = newTotalSeats - plan.BaseSeats,

MaxAutoscaleSmSeats = MaxAutoscaleSeats,

SmServiceAccountsAdjustment = ServiceAccountAdjustment,
SmServiceAccounts = newTotalServiceAccounts,
SmServiceAccountsExcludingBase = newTotalServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault(),

MaxAutoscaleSmServiceAccounts = MaxAutoscaleServiceAccounts,

MaxAutoscaleSmSeatsChanged =
MaxAutoscaleSeats.GetValueOrDefault() != organization.MaxAutoscaleSmSeats.GetValueOrDefault(),
MaxAutoscaleSmServiceAccountsChanged =
MaxAutoscaleServiceAccounts.GetValueOrDefault() != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
};

return orgUpdate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public OrganizationResponseModel(Organization organization, string obj = "organi
BusinessTaxNumber = organization.BusinessTaxNumber;
BillingEmail = organization.BillingEmail;
Plan = new PlanResponseModel(StaticStore.PasswordManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType));
SecretsManagerPlan = new PlanResponseModel(StaticStore.SecretManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType));
PlanType = organization.PlanType;
Seats = organization.Seats;
MaxAutoscaleSeats = organization.MaxAutoscaleSeats;
Expand Down Expand Up @@ -65,6 +66,7 @@ public OrganizationResponseModel(Organization organization, string obj = "organi
public string BusinessTaxNumber { get; set; }
public string BillingEmail { get; set; }
public PlanResponseModel Plan { get; set; }
public PlanResponseModel SecretsManagerPlan { get; set; }
public PlanType PlanType { get; set; }
public int? Seats { get; set; }
public int? MaxAutoscaleSeats { get; set; } = null;
Expand Down
9 changes: 6 additions & 3 deletions src/Api/Models/Response/PlanResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ public PlanResponseModel(Plan plan, string obj = "plan")

AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
BaseServiceAccount = plan.BaseServiceAccount;
MaxServiceAccount = plan.MaxServiceAccount;
MaxServiceAccounts = plan.MaxServiceAccounts;
MaxAdditionalServiceAccounts = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan.HasAdditionalServiceAccountOption;
MaxProjects = plan.MaxProjects;
BitwardenProduct = plan.BitwardenProduct;
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
}

public PlanType Type { get; set; }
Expand Down Expand Up @@ -105,10 +107,11 @@ public PlanResponseModel(Plan plan, string obj = "plan")
public decimal SeatPrice { get; set; }
public decimal AdditionalStoragePricePerGb { get; set; }
public decimal PremiumAccessOptionPrice { get; set; }

public string StripeServiceAccountPlanId { get; set; }
public decimal? AdditionalPricePerServiceAccount { get; set; }
public short? BaseServiceAccount { get; set; }
public short? MaxServiceAccount { get; set; }
public short? MaxServiceAccounts { get; set; }
public short? MaxAdditionalServiceAccounts { get; set; }
public bool HasAdditionalServiceAccountOption { get; set; }
public short? MaxProjects { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions src/Api/Models/Response/ProfileOrganizationResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga
UseApi = organization.UseApi;
UseResetPassword = organization.UseResetPassword;
UseSecretsManager = organization.UseSecretsManager;
UsePasswordManager = organization.UsePasswordManager;
UsersGetPremium = organization.UsersGetPremium;
UseCustomPermissions = organization.UseCustomPermissions;
UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually ||
Expand Down Expand Up @@ -82,6 +83,7 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga
public bool UseApi { get; set; }
public bool UseResetPassword { get; set; }
public bool UseSecretsManager { get; set; }
public bool UsePasswordManager { get; set; }
public bool UsersGetPremium { get; set; }
public bool UseCustomPermissions { get; set; }
public bool UseActivateAutofillPolicy { get; set; }
Expand Down
5 changes: 5 additions & 0 deletions src/Api/Models/Response/SubscriptionResponseModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
Expand Down Expand Up @@ -82,13 +83,17 @@ public BillingSubscriptionItem(SubscriptionInfo.BillingSubscription.BillingSubsc
Interval = item.Interval;
Quantity = item.Quantity;
SponsoredSubscriptionItem = item.SponsoredSubscriptionItem;
AddonSubscriptionItem = item.AddonSubscriptionItem;
BitwardenProduct = item.BitwardenProduct;
}

public string Name { get; set; }
public decimal Amount { get; set; }
public int Quantity { get; set; }
public string Interval { get; set; }
public bool SponsoredSubscriptionItem { get; set; }
public bool AddonSubscriptionItem { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Bit.Core.Auth.Identity;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;

#if !OSS
using Bit.Commercial.Core.SecretsManager;
Expand Down Expand Up @@ -133,6 +134,7 @@ public void ConfigureServices(IServiceCollection services)
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddOrganizationSubscriptionServices();
services.AddCoreLocalizationServices();

//health check
Expand Down
1 change: 1 addition & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class FeatureFlagKeys
public const string DisplayEuEnvironment = "display-eu-environment";
public const string DisplayLowKdfIterationWarning = "display-kdf-iteration-warning";
public const string TrustedDeviceEncryption = "trusted-device-encryption";
public const string SecretsManagerBilling = "sm-ga-billing";

public static List<string> GetAllKeys()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{#>FullHtmlLayout}}
<table width="100%" cellpadding="0" cellspacing="0"
style="margin: 0; box-sizing: border-box; ">
<tr
style="margin: 0; box-sizing: border-box; ">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;"
valign="top">
Your organization has reached the Secrets Manager seat limit of {{MaxSeatCount}} and new members cannot be invited.
</td>
</tr>
<tr
style="margin: 0; box-sizing: border-box; ">
<td class="content-block last"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;"
valign="top">
For more information, please refer to the following help article:
<a href="https://bitwarden.com/help/managing-users" class="inline-link">
Member management
</a>
<br class="line-break" />
<br class="line-break" />
</td>
</tr>
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
<a href="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/billing/subscription" clicktracking=off target="_blank" rel="noopener noreferrer" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #175DDC; border-color: #175DDC; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Manage subscription
</a>
<br class="line-break" />
</td>
</tr>
</table>
{{/FullHtmlLayout}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{#>BasicTextLayout}}
Your organization has reached the Secrets Manager seat limit of {{MaxSeatCount}} and new members cannot be invited.

For more information, please refer to the following help article: https://bitwarden.com/help/managing-users
{{/BasicTextLayout}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{#>FullHtmlLayout}}
<table width="100%" cellpadding="0" cellspacing="0"
style="margin: 0; box-sizing: border-box; ">
<tr
style="margin: 0; box-sizing: border-box; ">
<td class="content-block"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;"
valign="top">
Your organization has reached the Secrets Manager service accounts limit of {{MaxServiceAccountsCount}}. New service accounts cannot be created
</td>
</tr>
<tr
style="margin: 0; box-sizing: border-box; ">
<td class="content-block last"
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;"
valign="top">
For more information, please refer to the following help article:
<a href="https://bitwarden.com/help/managing-users" class="inline-link">
Member management
</a>
<br class="line-break" />
<br class="line-break" />
</td>
</tr>
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
<a href="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/billing/subscription" clicktracking=off target="_blank" rel="noopener noreferrer" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #175DDC; border-color: #175DDC; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Manage subscription
</a>
<br class="line-break" />
</td>
</tr>
</table>
{{/FullHtmlLayout}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{#>BasicTextLayout}}
Your organization has reached the Secrets Manager service accounts limit of {{MaxServiceAccountsCount}}. New service accounts cannot be created

For more information, please refer to the following help article: https://bitwarden.com/help/managing-users
{{/BasicTextLayout}}
Loading

0 comments on commit f580f96

Please sign in to comment.