Skip to content

Commit

Permalink
Merge branch 'main' into pm-12358-verified-sso-domain
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmccannon authored Oct 4, 2024
2 parents d95eab7 + c449886 commit 04f0e2c
Show file tree
Hide file tree
Showing 102 changed files with 28,386 additions and 164 deletions.
83 changes: 83 additions & 0 deletions src/Admin/Billing/Controllers/MigrateProvidersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Bit.Admin.Billing.Models;
using Bit.Admin.Enums;
using Bit.Admin.Utilities;
using Bit.Core.Billing.Migration.Models;
using Bit.Core.Billing.Migration.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Bit.Admin.Billing.Controllers;

[Authorize]
[Route("migrate-providers")]
[SelfHosted(NotSelfHostedOnly = true)]
public class MigrateProvidersController(
IProviderMigrator providerMigrator) : Controller
{
[HttpGet]
[RequirePermission(Permission.Tools_MigrateProviders)]
public IActionResult Index()
{
return View(new MigrateProvidersRequestModel());
}

[HttpPost]
[RequirePermission(Permission.Tools_MigrateProviders)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> PostAsync(MigrateProvidersRequestModel request)
{
var providerIds = GetProviderIdsFromInput(request.ProviderIds);

if (providerIds.Count == 0)
{
return RedirectToAction("Index");
}

foreach (var providerId in providerIds)
{
await providerMigrator.Migrate(providerId);
}

return RedirectToAction("Results", new { ProviderIds = string.Join("\r\n", providerIds) });
}

[HttpGet("results")]
[RequirePermission(Permission.Tools_MigrateProviders)]
public async Task<IActionResult> ResultsAsync(MigrateProvidersRequestModel request)
{
var providerIds = GetProviderIdsFromInput(request.ProviderIds);

if (providerIds.Count == 0)
{
return View(Array.Empty<ProviderMigrationResult>());
}

var results = await Task.WhenAll(providerIds.Select(providerMigrator.GetResult));

return View(results);
}

[HttpGet("results/{providerId:guid}")]
[RequirePermission(Permission.Tools_MigrateProviders)]
public async Task<IActionResult> DetailsAsync([FromRoute] Guid providerId)
{
var result = await providerMigrator.GetResult(providerId);

if (result == null)
{
return RedirectToAction("Index");
}

return View(result);
}

private static List<Guid> GetProviderIdsFromInput(string text) => !string.IsNullOrEmpty(text)
? text.Split(
["\r\n", "\r", "\n"],
StringSplitOptions.TrimEntries
)
.Select(id => new Guid(id))
.ToList()
: [];
}
10 changes: 10 additions & 0 deletions src/Admin/Billing/Models/MigrateProvidersRequestModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;

namespace Bit.Admin.Billing.Models;

public class MigrateProvidersRequestModel
{
[Required]
[Display(Name = "Provider IDs")]
public string ProviderIds { get; set; }
}
39 changes: 39 additions & 0 deletions src/Admin/Billing/Views/MigrateProviders/Details.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@using System.Text.Json
@model Bit.Core.Billing.Migration.Models.ProviderMigrationResult
@{
ViewData["Title"] = "Results";
}

<h1>Migrate Providers</h1>
<h2>Migration Details: @Model.ProviderName</h2>
<dl class="row">
<dt class="col-sm-4 col-lg-3">Id</dt>
<dd class="col-sm-8 col-lg-9"><code>@Model.ProviderId</code></dd>

<dt class="col-sm-4 col-lg-3">Result</dt>
<dd class="col-sm-8 col-lg-9">@Model.Result</dd>
</dl>
<h3>Client Organizations</h3>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Result</th>
<th>Previous State</th>
</tr>
</thead>
<tbody>
@foreach (var clientResult in Model.Clients)
{
<tr>
<td>@clientResult.OrganizationId</td>
<td>@clientResult.OrganizationName</td>
<td>@clientResult.Result</td>
<td><pre>@Html.Raw(JsonSerializer.Serialize(clientResult.PreviousState))</pre></td>
</tr>
}
</tbody>
</table>
</div>
46 changes: 46 additions & 0 deletions src/Admin/Billing/Views/MigrateProviders/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@model Bit.Admin.Billing.Models.MigrateProvidersRequestModel;
@{
ViewData["Title"] = "Migrate Providers";
}

<h1>Migrate Providers</h1>
<h2>Bulk Consolidated Billing Migration Tool</h2>
<section>
<p>
This tool allows you to provide a list of IDs for Providers that you would like to migrate to Consolidated Billing.
Because of the expensive nature of the operation, you can only migrate 10 Providers at a time.
</p>
<p class="alert alert-warning">
Updates made through this tool are irreversible without manual intervention.
</p>
<p>Example Input (Please enter each Provider ID separated by a new line):</p>
<div class="card">
<div class="card-body">
<pre class="mb-0">f513affc-2290-4336-879e-21ec3ecf3e78
f7a5cb0d-4b74-445c-8d8c-232d1d32bbe2
bf82d3cf-0e21-4f39-b81b-ef52b2fc6a3a
174e82fc-70c3-448d-9fe7-00bad2a3ab00
22a4bbbf-58e3-4e4c-a86a-a0d7caf4ff14</pre>
</div>
</div>
<form method="post" asp-controller="MigrateProviders" asp-action="Post" class="mt-2">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ProviderIds"></label>
<textarea rows="10" class="form-control" type="text" asp-for="ProviderIds"></textarea>
</div>
<div class="form-group">
<input type="submit" value="Run" class="btn btn-primary mb-2"/>
</div>
</form>
<form method="get" asp-controller="MigrateProviders" asp-action="Results" class="mt-2">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ProviderIds"></label>
<textarea rows="10" class="form-control" type="text" asp-for="ProviderIds"></textarea>
</div>
<div class="form-group">
<input type="submit" value="See Previous Results" class="btn btn-primary mb-2"/>
</div>
</form>
</section>
28 changes: 28 additions & 0 deletions src/Admin/Billing/Views/MigrateProviders/Results.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@model Bit.Core.Billing.Migration.Models.ProviderMigrationResult[]
@{
ViewData["Title"] = "Results";
}

<h1>Migrate Providers</h1>
<h2>Results</h2>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Result</th>
</tr>
</thead>
<tbody>
@foreach (var result in Model)
{
<tr>
<td><a href="@Url.Action("Details", "MigrateProviders", new { providerId = result.ProviderId })">@result.ProviderId</a></td>
<td>@result.ProviderName</td>
<td>@result.Result</td>
</tr>
}
</tbody>
</table>
</div>
3 changes: 2 additions & 1 deletion src/Admin/Enums/Permissions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ public enum Permission
Tools_ManageTaxRates,
Tools_ManageStripeSubscriptions,
Tools_CreateEditTransaction,
Tools_ProcessStripeEvents
Tools_ProcessStripeEvents,
Tools_MigrateProviders
}
3 changes: 3 additions & 0 deletions src/Admin/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Bit.Admin.Services;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Migration;

#if !OSS
using Bit.Commercial.Core.Utilities;
Expand Down Expand Up @@ -88,8 +89,10 @@ public void ConfigureServices(IServiceCollection services)
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddScoped<IAccessControlService, AccessControlService>();
services.AddDistributedCache(globalSettings);
services.AddBillingOperations();
services.AddHttpClient();
services.AddProviderMigration();

#if OSS
services.AddOosServices();
Expand Down
1 change: 1 addition & 0 deletions src/Admin/Utilities/RolePermissionMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ public static class RolePermissionMapping
Permission.Tools_ManageStripeSubscriptions,
Permission.Tools_CreateEditTransaction,
Permission.Tools_ProcessStripeEvents,
Permission.Tools_MigrateProviders
}
},
{ "sales", new List<Permission>
Expand Down
9 changes: 8 additions & 1 deletion src/Admin/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
var canManageTaxRates = AccessControlService.UserHasPermission(Permission.Tools_ManageTaxRates);
var canManageStripeSubscriptions = AccessControlService.UserHasPermission(Permission.Tools_ManageStripeSubscriptions);
var canProcessStripeEvents = AccessControlService.UserHasPermission(Permission.Tools_ProcessStripeEvents);
var canMigrateProviders = AccessControlService.UserHasPermission(Permission.Tools_MigrateProviders);

var canViewTools = canChargeBraintree || canCreateTransaction || canPromoteAdmin ||
canGenerateLicense || canManageTaxRates || canManageStripeSubscriptions;
Expand Down Expand Up @@ -108,12 +109,18 @@
Manage Stripe Subscriptions
</a>
}
@if (canProcessStripeEvents)
@if (canProcessStripeEvents)
{
<a class="dropdown-item" asp-controller="ProcessStripeEvents" asp-action="Index">
Process Stripe Events
</a>
}
@if (canMigrateProviders)
{
<a class="dropdown-item" asp-controller="MigrateProviders" asp-action="index">
Migrate Providers
</a>
}
</div>
</li>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ public async Task HandleAsync(Event parsedEvent)
var (organizationId, userId, providerId) = _stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata);
var subCanceled = subscription.Status == StripeSubscriptionStatus.Canceled;

const string providerMigrationCancellationComment = "Cancelled as part of provider migration to Consolidated Billing";

if (!subCanceled)
{
return;
}

if (organizationId.HasValue)
if (organizationId.HasValue && subscription is not { CancellationDetails.Comment: providerMigrationCancellationComment })
{
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/AdminConsole/Entities/Organization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
/// they have Can Manage permissions for.
/// </summary>
public bool LimitCollectionCreationDeletion { get; set; }

/// <summary>
/// If set to true, admins, owners, and some custom users can read/write all collections and items in the Admin Console.
/// If set to false, users generally need collection-level permissions to read/write a collection or its items.
Expand Down
32 changes: 32 additions & 0 deletions src/Core/Billing/Entities/ClientOrganizationMigrationRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Utilities;

#nullable enable

namespace Bit.Core.Billing.Entities;

public class ClientOrganizationMigrationRecord : ITableObject<Guid>
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public Guid ProviderId { get; set; }
public PlanType PlanType { get; set; }
public int Seats { get; set; }
public short? MaxStorageGb { get; set; }
[MaxLength(50)] public string GatewayCustomerId { get; set; } = null!;
[MaxLength(50)] public string GatewaySubscriptionId { get; set; } = null!;
public DateTime? ExpirationDate { get; set; }
public int? MaxAutoscaleSeats { get; set; }
public OrganizationStatusType Status { get; set; }

public void SetNewId()
{
if (Id == default)
{
Id = CoreHelpers.GenerateComb();
}
}
}
23 changes: 23 additions & 0 deletions src/Core/Billing/Migration/Models/ClientMigrationTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Bit.Core.Billing.Migration.Models;

public enum ClientMigrationProgress
{
Started = 1,
MigrationRecordCreated = 2,
SubscriptionEnded = 3,
Completed = 4,

Reversing = 5,
ResetOrganization = 6,
RecreatedSubscription = 7,
RemovedMigrationRecord = 8,
Reversed = 9
}

public class ClientMigrationTracker
{
public Guid ProviderId { get; set; }
public Guid OrganizationId { get; set; }
public string OrganizationName { get; set; }
public ClientMigrationProgress Progress { get; set; } = ClientMigrationProgress.Started;
}
Loading

0 comments on commit 04f0e2c

Please sign in to comment.