mirror of
https://github.com/bitwarden/server.git
synced 2025-02-16 01:51:21 +01:00
[AC-1577] [AC-1575] Prefill Bitwarden portal values etc (#3155)
* [AC-1575] Don't overwrite existing org information when changing plans * [AC-1577] Prefill SM configuration section when starting trial
This commit is contained in:
parent
4ec01b0ef0
commit
60eab8a28e
@ -5,6 +5,7 @@ using Bit.Admin.Utilities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
@ -199,6 +200,14 @@ public class OrganizationsController : Controller
|
||||
{
|
||||
var organization = await GetOrganization(id, model);
|
||||
|
||||
if (organization.UseSecretsManager &&
|
||||
!organization.SecretsManagerBeta
|
||||
&& StaticStore.GetSecretsManagerPlan(organization.PlanType) == null
|
||||
)
|
||||
{
|
||||
throw new BadRequestException("Plan does not support Secrets Manager");
|
||||
}
|
||||
|
||||
await _organizationRepository.ReplaceAsync(organization);
|
||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization, _currentContext)
|
||||
|
@ -28,9 +28,9 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
public OrganizationEditModel(Organization org, Provider provider, IEnumerable<OrganizationUserUserDetails> orgUsers,
|
||||
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
|
||||
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
|
||||
GlobalSettings globalSettings, int secrets, int projects, int serviceAccounts, int smSeats)
|
||||
GlobalSettings globalSettings, int secrets, int projects, int serviceAccounts, int occupiedSmSeats)
|
||||
: base(org, provider, connections, orgUsers, ciphers, collections, groups, policies, secrets, projects,
|
||||
serviceAccounts, smSeats)
|
||||
serviceAccounts, occupiedSmSeats)
|
||||
{
|
||||
BillingInfo = billingInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
@ -145,13 +145,26 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
public int? SmSeats { get; set; }
|
||||
[Display(Name = "Max Autoscale Seats")]
|
||||
public int? MaxAutoscaleSmSeats { get; set; }
|
||||
[Display(Name = "Max Service Accounts")]
|
||||
[Display(Name = "Service Accounts")]
|
||||
public int? SmServiceAccounts { get; set; }
|
||||
[Display(Name = "Max Autoscale Service Accounts")]
|
||||
public int? MaxAutoscaleSmServiceAccounts { get; set; }
|
||||
[Display(Name = "Secrets Manager Beta")]
|
||||
public bool SecretsManagerBeta { get; set; }
|
||||
|
||||
/**
|
||||
* Creates a Plan[] object for use in Javascript
|
||||
* This is mapped manually below to provide some type safety in case the plan objects change
|
||||
* Add mappings for individual properties as you need them
|
||||
*/
|
||||
public IEnumerable<Dictionary<string, object>> GetPlansHelper() =>
|
||||
StaticStore.SecretManagerPlans.Select(p =>
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "type", p.Type },
|
||||
{ "baseServiceAccount", p.BaseServiceAccount }
|
||||
});
|
||||
|
||||
public Organization CreateOrganization(Provider provider)
|
||||
{
|
||||
BillingEmail = provider.BillingEmail;
|
||||
|
@ -13,7 +13,7 @@ public class OrganizationViewModel
|
||||
public OrganizationViewModel(Organization org, Provider provider, IEnumerable<OrganizationConnection> connections,
|
||||
IEnumerable<OrganizationUserUserDetails> orgUsers, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<Group> groups, IEnumerable<Policy> policies, int secretsCount, int projectCount, int serviceAccountsCount,
|
||||
int smSeatsCount)
|
||||
int occupiedSmSeatsCount)
|
||||
|
||||
{
|
||||
Organization = org;
|
||||
@ -39,10 +39,10 @@ public class OrganizationViewModel
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Admin && u.Status == organizationUserStatus)
|
||||
.Select(u => u.Email));
|
||||
Secrets = secretsCount;
|
||||
Projects = projectCount;
|
||||
ServiceAccounts = serviceAccountsCount;
|
||||
SmSeats = smSeatsCount;
|
||||
SecretsCount = secretsCount;
|
||||
ProjectsCount = projectCount;
|
||||
ServiceAccountsCount = serviceAccountsCount;
|
||||
OccupiedSmSeatsCount = occupiedSmSeatsCount;
|
||||
}
|
||||
|
||||
public Organization Organization { get; set; }
|
||||
@ -59,9 +59,9 @@ public class OrganizationViewModel
|
||||
public int GroupCount { get; set; }
|
||||
public int PolicyCount { get; set; }
|
||||
public bool HasPublicPrivateKeys { get; set; }
|
||||
public int Secrets { get; set; }
|
||||
public int Projects { get; set; }
|
||||
public int ServiceAccounts { get; set; }
|
||||
public int SmSeats { get; set; }
|
||||
public int SecretsCount { get; set; }
|
||||
public int ProjectsCount { get; set; }
|
||||
public int ServiceAccountsCount { get; set; }
|
||||
public int OccupiedSmSeatsCount { get; set; }
|
||||
public bool UseSecretsManager => Organization.UseSecretsManager;
|
||||
}
|
||||
|
@ -12,25 +12,45 @@
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_OrganizationFormScripts")
|
||||
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
document.getElementById('teams-trial').addEventListener('click', () => {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)PlanType.Free)') {
|
||||
alert('Organization is not on a free plan.');
|
||||
return;
|
||||
}
|
||||
togglePlanSettings('@((byte)Bit.Core.Enums.PlanType.TeamsAnnually)');
|
||||
setTrialDefaults('@((byte)PlanType.TeamsAnnually)');
|
||||
togglePlanFeatures('@((byte)PlanType.TeamsAnnually)');
|
||||
document.getElementById('@(nameof(Model.Plan))').value = 'Teams (Trial)';
|
||||
});
|
||||
document.getElementById('enterprise-trial').addEventListener('click', () => {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)PlanType.Free)') {
|
||||
alert('Organization is not on a free plan.');
|
||||
return;
|
||||
}
|
||||
togglePlanSettings('@((byte)Bit.Core.Enums.PlanType.EnterpriseAnnually)');
|
||||
setTrialDefaults('@((byte)PlanType.EnterpriseAnnually)');
|
||||
togglePlanFeatures('@((byte)PlanType.EnterpriseAnnually)');
|
||||
document.getElementById('@(nameof(Model.Plan))').value = 'Enterprise (Trial)';
|
||||
});
|
||||
|
||||
function setTrialDefaults(planType) {
|
||||
// Plan
|
||||
document.getElementById('@(nameof(Model.PlanType))').value = planType;
|
||||
// Password Manager
|
||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxStorageGb))').value = '1';
|
||||
// Secret Manager
|
||||
if (document.getElementById('@(nameof(Model.UseSecretsManager))').checked) {
|
||||
document.getElementById('@(nameof(Model.SmSeats))').value = '10';
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = getPlan(planType)?.baseServiceAccount;
|
||||
}
|
||||
// Licensing
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
@ -33,16 +33,16 @@
|
||||
<dd class="col-sm-8 col-lg-9">@Model.CollectionCount</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Secrets</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.Secrets: "N/A")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.SecretsCount: "N/A")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Projects</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.Projects: "N/A")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.ProjectsCount: "N/A")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Service Accounts</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.ServiceAccounts: "N/A")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.ServiceAccountsCount: "N/A")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Secrets Manager Seats</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.SmSeats: "N/A" )</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.UseSecretsManager ? Model.OccupiedSmSeatsCount: "N/A" )</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Groups</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.GroupCount</dd>
|
||||
|
@ -5,10 +5,10 @@
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_OrganizationFormScripts")
|
||||
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
togglePlanSettings('@((byte)Model.PlanType)');
|
||||
togglePlanFeatures('@((byte)Model.PlanType)');
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
@ -83,7 +83,7 @@
|
||||
var planTypes = Enum.GetValues<PlanType>()
|
||||
.Where(p => Model.Provider == null || p is >= PlanType.TeamsMonthly and <= PlanType.EnterpriseAnnually)
|
||||
.Select(e => new SelectListItem
|
||||
{
|
||||
{
|
||||
Value = ((int)e).ToString(),
|
||||
Text = e.GetDisplayAttribute()?.GetName() ?? e.ToString()
|
||||
})
|
||||
@ -176,7 +176,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if (canViewPlan)
|
||||
{
|
||||
<h2>Password Manager Configuration</h2>
|
||||
@ -212,7 +212,7 @@
|
||||
|
||||
@if (canViewPlan)
|
||||
{
|
||||
<div id="organization-secrets-configuration" hidden="@(!Model.UseSecretsManager)">
|
||||
<div id="organization-secrets-configuration" hidden="@(!Model.UseSecretsManager || Model.SecretsManagerBeta)">
|
||||
<h2>Secrets Manager Configuration</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
@ -265,7 +265,7 @@
|
||||
}
|
||||
|
||||
@if (canViewBilling)
|
||||
{
|
||||
{
|
||||
<h2>Billing</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
@ -315,7 +315,7 @@
|
||||
<label asp-for="GatewaySubscriptionId"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewaySubscriptionId" readonly='@(!canEditBilling)'>
|
||||
@if (canLaunchGateway)
|
||||
@if (canLaunchGateway)
|
||||
{
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-secondary" type="button" id="gateway-subscription-link">
|
||||
@ -328,4 +328,4 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</form>
|
||||
|
@ -1,10 +1,12 @@
|
||||
@model OrganizationEditModel
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
document.getElementById('@(nameof(Model.PlanType))').addEventListener('change', () => {
|
||||
const selectEl = document.getElementById('@(nameof(Model.PlanType))');
|
||||
const selectText = selectEl.options[selectEl.selectedIndex].text;
|
||||
document.getElementById('@(nameof(Model.Plan))').value = selectText;
|
||||
togglePlanSettings(selectEl.options[selectEl.selectedIndex].value);
|
||||
togglePlanFeatures(selectEl.options[selectEl.selectedIndex].value);
|
||||
});
|
||||
document.getElementById('gateway-customer-link')?.addEventListener('click', () => {
|
||||
const gateway = document.getElementById('@(nameof(Model.Gateway))');
|
||||
@ -12,9 +14,9 @@
|
||||
if (!gateway || gateway.value === '' || !customerId || customerId.value === '') {
|
||||
return;
|
||||
}
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
if (gateway.value === '@((byte)GatewayType.Stripe)') {
|
||||
window.open('https://dashboard.stripe.com/customers/' + customerId.value, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
} else if (gateway.value === '@((byte)GatewayType.Braintree)') {
|
||||
window.open('https://www.braintreegateway.com/merchants/@(Model.BraintreeMerchantId)/'
|
||||
+ customerId.value, '_blank');
|
||||
}
|
||||
@ -25,41 +27,45 @@
|
||||
if (!gateway || gateway.value === '' || !subId || subId.value === '') {
|
||||
return;
|
||||
}
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
if (gateway.value === '@((byte)GatewayType.Stripe)') {
|
||||
window.open('https://dashboard.stripe.com/subscriptions/' + subId.value, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
} else if (gateway.value === '@((byte)GatewayType.Braintree)') {
|
||||
window.open('https://www.braintreegateway.com/merchants/@(Model.BraintreeMerchantId)/' +
|
||||
'subscriptions/' + subId.value, '_blank');
|
||||
}
|
||||
});
|
||||
document.getElementById('@(nameof(Model.UseSecretsManager))').addEventListener('change', (event) => {
|
||||
document.getElementById('organization-secrets-configuration').hidden = !event.target.checked;
|
||||
document.getElementById('organization-secrets-configuration').hidden = !event.target.checked;
|
||||
|
||||
if (event.target.checked) {
|
||||
setInitialSecretsManagerConfiguration();
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('@(nameof(Model.SmSeats))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxAutoscaleSmSeats))').value = '';
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxAutoscaleSmServiceAccounts))').value = '';
|
||||
// SM beta requires SM access
|
||||
document.getElementById('@(nameof(Model.SecretsManagerBeta))').checked = false;
|
||||
clearSecretsManagerConfiguration();
|
||||
});
|
||||
|
||||
document.getElementById('@(nameof(Model.SecretsManagerBeta))').addEventListener('change', (event) => {
|
||||
document.getElementById('organization-secrets-configuration').hidden = event.target.checked;
|
||||
|
||||
if (event.target.checked) {
|
||||
// SM beta requires SM access
|
||||
document.getElementById('@(nameof(Model.UseSecretsManager))').checked = true;
|
||||
// SM Beta orgs do not have subscription limits
|
||||
clearSecretsManagerConfiguration();
|
||||
return;
|
||||
}
|
||||
|
||||
setInitialSecretsManagerConfiguration();
|
||||
});
|
||||
})();
|
||||
|
||||
function togglePlanSettings(planType) {
|
||||
document.getElementById('@(nameof(Model.PlanType))').value = planType;
|
||||
function togglePlanFeatures(planType) {
|
||||
switch(planType) {
|
||||
case '@((byte)Bit.Core.Enums.PlanType.TeamsMonthly)':
|
||||
case '@((byte)Bit.Core.Enums.PlanType.TeamsAnnually)':
|
||||
// Plan
|
||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxStorageGb))').value = '1';
|
||||
// Secrets
|
||||
if (document.getElementById('@(nameof(Model.UseSecretsManager))').checked) {
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = '50';
|
||||
}
|
||||
// Features
|
||||
case '@((byte)PlanType.TeamsMonthly)':
|
||||
case '@((byte)PlanType.TeamsAnnually)':
|
||||
document.getElementById('@(nameof(Model.UsePolicies))').checked = false;
|
||||
document.getElementById('@(nameof(Model.UseSso))').checked = false;
|
||||
document.getElementById('@(nameof(Model.UseGroups))').checked = true;
|
||||
@ -73,23 +79,10 @@
|
||||
document.getElementById('@(nameof(Model.SelfHost))').checked = false;
|
||||
document.getElementById('@(nameof(Model.UseResetPassword))').checked = false;
|
||||
document.getElementById('@(nameof(Model.UseScim))').checked = false;
|
||||
// Licensing
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
||||
break;
|
||||
|
||||
case '@((byte)Bit.Core.Enums.PlanType.EnterpriseMonthly)':
|
||||
case '@((byte)Bit.Core.Enums.PlanType.EnterpriseAnnually)':
|
||||
// Plan
|
||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxStorageGb))').value = '1';
|
||||
// Secrets
|
||||
if (document.getElementById('@(nameof(Model.UseSecretsManager))').checked) {
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = '200';
|
||||
}
|
||||
// Features
|
||||
case '@((byte)PlanType.EnterpriseMonthly)':
|
||||
case '@((byte)PlanType.EnterpriseAnnually)':
|
||||
document.getElementById('@(nameof(Model.UsePolicies))').checked = true;
|
||||
document.getElementById('@(nameof(Model.UseSso))').checked = true;
|
||||
document.getElementById('@(nameof(Model.UseGroups))').checked = true;
|
||||
@ -103,12 +96,41 @@
|
||||
document.getElementById('@(nameof(Model.SelfHost))').checked = true;
|
||||
document.getElementById('@(nameof(Model.UseResetPassword))').checked = true;
|
||||
document.getElementById('@(nameof(Model.UseScim))').checked = true;
|
||||
// Licensing
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Set Secrets Manager values based on current usage (for migrating from SM beta or reinstating an old subscription)
|
||||
*/
|
||||
function setInitialSecretsManagerConfiguration() {
|
||||
const planType = document.getElementById('@(nameof(Model.PlanType))').value;
|
||||
|
||||
// Seats
|
||||
document.getElementById('@(nameof(Model.SmSeats))').value = Math.max(@Model.OccupiedSmSeatsCount, 1);
|
||||
|
||||
// Service accounts
|
||||
const baseServiceAccounts = getPlan(planType)?.baseServiceAccount ?? 0;
|
||||
if (planType !== '@((byte)PlanType.Free)' && @Model.ServiceAccountsCount > baseServiceAccounts) {
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = @Model.ServiceAccountsCount;
|
||||
} else {
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = baseServiceAccounts;
|
||||
}
|
||||
|
||||
// Clear autoscale values (no defaults)
|
||||
document.getElementById('@(nameof(Model.MaxAutoscaleSmSeats))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxAutoscaleSmServiceAccounts))').value = '';
|
||||
}
|
||||
|
||||
function clearSecretsManagerConfiguration() {
|
||||
document.getElementById('@(nameof(Model.SmSeats))').value = '';
|
||||
document.getElementById('@(nameof(Model.SmServiceAccounts))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxAutoscaleSmSeats))').value = '';
|
||||
document.getElementById('@(nameof(Model.MaxAutoscaleSmServiceAccounts))').value = '';
|
||||
}
|
||||
|
||||
function getPlan(planType) {
|
||||
const plans = @Html.Raw(Json.Serialize(Model.GetPlansHelper()));
|
||||
return plans.find(p => p.type == planType);
|
||||
}
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user