1
0
mirror of https://github.com/bitwarden/server synced 2026-01-09 12:03:21 +00:00

[AC-1218] Add ability to delete Provider Portals (#3973)

* add new classes

* initial commit

* revert the changes on this files

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* revert unnecessary changes

* Add a model

* add the delete token endpoint

* add a unit test for delete provider

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* add the delete provider method

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve the failing test

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve the delete request redirect issue

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* changes to correct the json issue

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve errors

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve pr comment

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* move ProviderDeleteTokenable to the adminConsole

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* Add feature flag

* resolve pr comments

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* add some unit test

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve the failing test

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* resolve test

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* add the remove feature flag

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* [AC-2378] Added `ProviderId` to PayPal transaction model (#3995)

* Added ProviderId to PayPal transaction model

* Fixed issue with parsing provider id

* [AC-1923] Add endpoint to create client organization (#3977)

* Add new endpoint for creating client organizations in consolidated billing

* Create empty org and then assign seats for code re-use

* Fixes made from debugging client side

* few more small fixes

* Vincent's feedback

* Bumped version to 2024.4.1 (#3997)

* [AC-1923] Add endpoint to create client organization (#3977)

* Add new endpoint for creating client organizations in consolidated billing

* Create empty org and then assign seats for code re-use

* Fixes made from debugging client side

* few more small fixes

* Vincent's feedback

* [AC-1923] Add endpoint to create client organization (#3977)

* Add new endpoint for creating client organizations in consolidated billing

* Create empty org and then assign seats for code re-use

* Fixes made from debugging client side

* few more small fixes

* Vincent's feedback

* add changes after merge conflict

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

---------

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>
Co-authored-by: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com>
Co-authored-by: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com>
Co-authored-by: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com>
This commit is contained in:
cyprain-okeke
2024-04-17 10:09:53 +01:00
committed by GitHub
parent ddbb031bcb
commit 6672019122
22 changed files with 567 additions and 11 deletions

View File

@@ -1,4 +1,5 @@
using System.Net;
using System.ComponentModel.DataAnnotations;
using System.Net;
using Bit.Admin.AdminConsole.Models;
using Bit.Admin.Enums;
using Bit.Admin.Utilities;
@@ -10,6 +11,7 @@ using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Entities;
using Bit.Core.Billing.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@@ -275,4 +277,64 @@ public class ProvidersController : Controller
return RedirectToAction("Edit", "Providers", new { id = providerId });
}
[HttpPost]
[SelfHosted(NotSelfHostedOnly = true)]
[RequirePermission(Permission.Provider_Edit)]
public async Task<IActionResult> Delete(Guid id, string providerName)
{
if (string.IsNullOrWhiteSpace(providerName))
{
return BadRequest("Invalid provider name");
}
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
if (providerOrganizations.Count > 0)
{
return BadRequest("You must unlink all clients before you can delete a provider");
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider is null)
{
return BadRequest("Provider does not exist");
}
if (!string.Equals(providerName.Trim(), provider.Name, StringComparison.OrdinalIgnoreCase))
{
return BadRequest("Invalid provider name");
}
await _providerService.DeleteAsync(provider);
return NoContent();
}
[HttpPost]
[SelfHosted(NotSelfHostedOnly = true)]
[RequirePermission(Permission.Provider_Edit)]
public async Task<IActionResult> DeleteInitiation(Guid id, string providerEmail)
{
var emailAttribute = new EmailAddressAttribute();
if (!emailAttribute.IsValid(providerEmail))
{
return BadRequest("Invalid provider admin email");
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider != null)
{
try
{
await _providerService.InitiateDeleteAsync(provider, providerEmail);
}
catch (BadRequestException ex)
{
return BadRequest(ex.Message);
}
}
return NoContent();
}
}

View File

@@ -6,7 +6,6 @@
@model ProviderEditModel
@{
ViewData["Title"] = "Provider: " + Model.Provider.DisplayName();
var canEdit = AccessControlService.UserHasPermission(Permission.Provider_Edit);
}
@@ -62,10 +61,89 @@
}
</form>
@await Html.PartialAsync("Organizations", Model)
@if (canEdit)
{
<div class="d-flex mt-4">
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
</div>
}
@if (canEdit)
{
<!-- Modals -->
<div class="modal fade rounded" id="requestDeletionModal" tabindex="-1" aria-labelledby="requestDeletionModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded">
<div class="p-3">
<h3 class="font-weight-bolder" id="exampleModalLabel">Request provider deletion</h3>
</div>
<div class="modal-body">
<span class="font-weight-light">
Enter the email of the provider admin that will receive the request to delete the provider portal.
</span>
<form>
<div class="form-group">
<label for="provider-email" class="col-form-label">Provider email</label>
<input type="email" class="form-control" id="provider-email">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary btn-pill" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-pill" onclick="initiateDeleteProvider('@Model.Provider.Id')">Send email request</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="DeleteModal" tabindex="-1" aria-labelledby="DeleteModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded">
<div class="p-3">
<h3 class="font-weight-bolder" id="exampleModalLabel">Delete provider</h3>
</div>
<div class="modal-body">
<span class="font-weight-light">
This action is permanent and irreversible. Enter the provide name to complete deletion of the provider and associated data.
</span>
<form>
<div class="form-group">
<label for="provider-name" class="col-form-label">Provider name</label>
<input type="text" class="form-control" id="provider-name">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary btn-pill" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-pill" onclick="deleteProvider('@Model.Provider.Id');">Delete provider</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="linkedWarningModal" tabindex="-1" role="dialog" aria-labelledby="linkedWarningModal" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content rounded">
<div class="modal-body">
<h4 class="font-weight-bolder">Cannot Delete @Model.Name</h4>
<p class="font-weight-lighter">you must unlink all clients before deleting @Model.Name</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary btn-pill" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary btn-pill" data-dismiss="modal">Ok</button>
</div>
</div>
</div>
</div>
<!-- End of Modal Section -->
<div class="d-flex mt-4">
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
@if (FeatureService.IsEnabled(FeatureFlagKeys.EnableDeleteProvider))
{
<div class="ml-auto d-flex">
<button class="btn btn-danger" onclick="openRequestDeleteModal(@Model.ProviderOrganizations.Count())">Request Delete</button>
<button id="requestDeletionBtn" hidden="hidden" data-toggle="modal" data-target="#requestDeletionModal"></button>
<button class="btn btn-outline-danger ml-2" onclick="openDeleteModal(@Model.ProviderOrganizations.Count())">Delete</button>
<button id="deleteBtn" hidden="hidden" data-toggle="modal" data-target="#DeleteModal"></button>
<button id="linkAccWarningBtn" hidden="hidden" data-toggle="modal" data-target="#linkedWarningModal"></button>
</div>
}
</div>
}

View File

@@ -17,4 +17,60 @@
}
return false;
}
function deleteProvider(id) {
const providerName = $('#DeleteModal input#provider-name').val();
$.ajax({
type: "POST",
url: `@Url.Action("Delete", "Providers")?id=${id}&providerName=${providerName}`,
dataType: 'json',
contentType: false,
processData: false,
success: function () {
$('#DeleteModal').modal('hide');
window.location.href = `@Url.Action("Index", "Providers")`;
},
error: function (response) {
alert("Error!: " + response.responseText);
}
});
}
function initiateDeleteProvider(id) {
const email = $('#requestDeletionModal input#provider-email').val();
const providerEmail = encodeURIComponent(email);
$.ajax({
type: "POST",
url: `@Url.Action("DeleteInitiation", "Providers")?id=${id}&providerEmail=${providerEmail}`,
dataType: 'json',
contentType: false,
processData: false,
success: function () {
$('#requestDeletionModal').modal('hide');
window.location.href = `@Url.Action("Index", "Providers")`;
},
error: function (response) {
alert("Error!: " + response.responseText);
}
});
}
function openDeleteModal(providerOrganizations) {
if (providerOrganizations > 0){
$('#linkAccWarningBtn').click()
} else {
$('#deleteBtn').click()
}
}
function openRequestDeleteModal(providerOrganizations) {
if (providerOrganizations > 0){
$('#linkAccWarningBtn').click()
} else {
$('#requestDeletionBtn').click()
}
}
</script>

View File

@@ -123,4 +123,40 @@ public class ProvidersController : Controller
return new ProviderResponseModel(response);
}
[HttpPost("{id}/delete-recover-token")]
[AllowAnonymous]
public async Task PostDeleteRecoverToken(Guid id, [FromBody] ProviderVerifyDeleteRecoverRequestModel model)
{
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
await _providerService.DeleteAsync(provider, model.Token);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id)
{
if (!_currentContext.ProviderProviderAdmin(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
await _providerService.DeleteAsync(provider);
}
}

View File

@@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.AdminConsole.Models.Request.Providers;
public class ProviderVerifyDeleteRecoverRequestModel
{
[Required]
public string Token { get; set; }
}

View File

@@ -0,0 +1,37 @@
using Newtonsoft.Json;
namespace Bit.Core.AdminConsole.Models.Business.Tokenables;
public class ProviderDeleteTokenable : Tokens.ExpiringTokenable
{
public const string ClearTextPrefix = "BwProviderId";
public const string DataProtectorPurpose = "ProviderDeleteDataProtector";
public const string TokenIdentifier = "ProviderDelete";
public string Identifier { get; set; } = TokenIdentifier;
public Guid Id { get; set; }
[JsonConstructor]
public ProviderDeleteTokenable()
{
}
[JsonConstructor]
public ProviderDeleteTokenable(DateTime expirationDate)
{
ExpirationDate = expirationDate;
}
public ProviderDeleteTokenable(Entities.Provider.Provider provider, int hoursTillExpiration)
{
Id = provider.Id;
ExpirationDate = DateTime.UtcNow.AddHours(hoursTillExpiration);
}
public bool IsValid(Entities.Provider.Provider provider)
{
return Id == provider.Id;
}
protected override bool TokenIsValid() => Identifier == TokenIdentifier && Id != default;
}

View File

@@ -26,5 +26,8 @@ public interface IProviderService
Task LogProviderAccessToOrganizationAsync(Guid organizationId);
Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId);
Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail);
Task InitiateDeleteAsync(Provider provider, string providerAdminEmail);
Task DeleteAsync(Provider provider, string token);
Task DeleteAsync(Provider provider);
}

View File

@@ -35,4 +35,7 @@ public class NoopProviderService : IProviderService
public Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid userId) => throw new NotImplementedException();
public Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail) => throw new NotImplementedException();
public Task InitiateDeleteAsync(Provider provider, string providerAdminEmail) => throw new NotImplementedException();
public Task DeleteAsync(Provider provider, string token) => throw new NotImplementedException();
public Task DeleteAsync(Provider provider) => throw new NotImplementedException();
}

View File

@@ -128,11 +128,12 @@ public static class FeatureFlagKeys
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
public const string PM5766AutomaticTax = "PM-5766-automatic-tax";
public const string PM5864DollarThreshold = "PM-5864-dollar-threshold";
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
public const string ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners";
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
public const string EnableConsolidatedBilling = "enable-consolidated-billing";
public const string AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section";
public const string UnassignedItemsBanner = "unassigned-items-banner";
public const string EnableDeleteProvider = "AC-1218-delete-provider";
public static List<string> GetAllKeys()
{

View File

@@ -3,5 +3,6 @@
public enum ApplicationCacheMessageType : byte
{
UpsertOrganizationAbility = 0,
DeleteOrganizationAbility = 1
DeleteOrganizationAbility = 1,
DeleteProviderAbility = 2,
}

View File

@@ -0,0 +1,37 @@
{{#>FullHtmlLayout}}
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<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: left;" valign="top" align="center">
We recently received your request to permanently delete the following Bitwarden provider:
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; 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;" valign="top">
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Name:</b> {{ProviderName}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">ID:</b> {{ProviderId}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Created:</b> {{ProviderCreationDate}} at {{ProviderCreationTime}} {{TimeZone}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Billing email address:</b> {{ProviderBillingEmail}}
</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: left;" valign="top" align="center">
Click the link below to delete your Bitwarden provider.
</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 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; text-align: left;" valign="top" align="center">
If you did not request this email to delete your Bitwarden provider, you can safely ignore it.
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
</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="{{{Url}}}" clicktracking=off target="_blank" 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;">
Delete Your Provider
</a>
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
</td>
</tr>
</table>
{{/FullHtmlLayout}}

View File

@@ -0,0 +1,15 @@
{{#>BasicTextLayout}}
We recently received your request to permanently delete the following Bitwarden provider:
- Name: {{ProviderName}}
- ID: {{ProviderId}}
- Created: {{ProviderCreationDate}} at {{ProviderCreationTime}} {{TimeZone}}
- Billing email address: {{ProviderBillingEmail}}
Click the link below to complete the deletion of your provider.
If you did not request this email to delete your Bitwarden provider, you can safely ignore it.
{{{Url}}}
{{/BasicTextLayout}}

View File

@@ -0,0 +1,21 @@
namespace Bit.Core.Models.Mail.Provider;
public class ProviderInitiateDeleteModel : BaseMailModel
{
public string Url => string.Format("{0}/verify-recover-delete-provider?providerId={1}&token={2}&name={3}",
WebVaultUrl,
ProviderId,
Token,
ProviderNameUrlEncoded);
public string WebVaultUrl { get; set; }
public string Token { get; set; }
public Guid ProviderId { get; set; }
public string SiteName { get; set; }
public string ProviderName { get; set; }
public string ProviderNameUrlEncoded { get; set; }
public string ProviderBillingEmail { get; set; }
public string ProviderCreationDate { get; set; }
public string ProviderCreationTime { get; set; }
public string TimeZone { get; set; }
}

View File

@@ -15,4 +15,5 @@ public interface IApplicationCacheService
Task UpsertOrganizationAbilityAsync(Organization organization);
Task UpsertProviderAbilityAsync(Provider provider);
Task DeleteOrganizationAbilityAsync(Guid organizationId);
Task DeleteProviderAbilityAsync(Guid providerId);
}

View File

@@ -78,5 +78,6 @@ public interface IMailService
Task SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
Task SendTrustedDeviceAdminApprovalEmailAsync(string email, DateTime utcNow, string ip, string deviceTypeAndIdentifier);
Task SendTrialInitiationEmailAsync(string email);
Task SendInitiateDeletProviderEmailAsync(string email, Provider provider, string token);
}

View File

@@ -267,6 +267,28 @@ public class HandlebarsMailService : IMailService
await _mailDeliveryService.SendEmailAsync(message);
}
public async Task SendInitiateDeletProviderEmailAsync(string email, Provider provider, string token)
{
var message = CreateDefaultMessage("Request to Delete Your Provider", email);
var model = new ProviderInitiateDeleteModel
{
Token = WebUtility.UrlEncode(token),
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
SiteName = _globalSettings.SiteName,
ProviderId = provider.Id,
ProviderName = CoreHelpers.SanitizeForEmail(provider.DisplayName(), false),
ProviderNameUrlEncoded = WebUtility.UrlEncode(provider.Name),
ProviderBillingEmail = provider.BillingEmail,
ProviderCreationDate = provider.CreationDate.ToLongDateString(),
ProviderCreationTime = provider.CreationDate.ToShortTimeString(),
TimeZone = _utcTimeZoneDisplay,
};
await AddMessageContentAsync(message, "Provider.InitiateDeleteProvider", model);
message.MetaData.Add("SendGridBypassListManagement", true);
message.Category = "InitiateDeleteProvider";
await _mailDeliveryService.SendEmailAsync(message);
}
public async Task SendPasswordlessSignInAsync(string returnUrl, string token, string email)
{
var message = CreateDefaultMessage("[Admin] Continue Logging In", email);

View File

@@ -85,6 +85,16 @@ public class InMemoryApplicationCacheService : IApplicationCacheService
return Task.FromResult(0);
}
public virtual Task DeleteProviderAbilityAsync(Guid providerId)
{
if (_providerAbilities != null && _providerAbilities.ContainsKey(providerId))
{
_providerAbilities.Remove(providerId);
}
return Task.FromResult(0);
}
private async Task InitOrganizationAbilitiesAsync()
{
var now = DateTime.UtcNow;

View File

@@ -64,4 +64,19 @@ public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCach
{
await base.DeleteOrganizationAbilityAsync(organizationId);
}
public override async Task DeleteProviderAbilityAsync(Guid providerId)
{
await base.DeleteProviderAbilityAsync(providerId);
var message = new ServiceBusMessage
{
Subject = _subName,
ApplicationProperties =
{
{ "type", (byte)ApplicationCacheMessageType.DeleteProviderAbility },
{ "id", providerId },
}
};
var task = _topicMessageSender.SendMessageAsync(message);
}
}

View File

@@ -268,5 +268,7 @@ public class NoopMailService : IMailService
{
return Task.FromResult(0);
}
public Task SendInitiateDeletProviderEmailAsync(string email, Provider provider, string token) => throw new NotImplementedException();
}

View File

@@ -3,6 +3,7 @@ using System.Reflection;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using AspNetCoreRateLimit;
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.Services;
using Bit.Core.AdminConsole.Services.Implementations;
using Bit.Core.AdminConsole.Services.NoopImplementations;
@@ -203,6 +204,14 @@ public static class ServiceCollectionExtensions
DuoUserStateTokenable.DataProtectorPurpose,
serviceProvider.GetDataProtectionProvider(),
serviceProvider.GetRequiredService<ILogger<DataProtectorTokenFactory<DuoUserStateTokenable>>>()));
services.AddSingleton<IDataProtectorTokenFactory<ProviderDeleteTokenable>>(serviceProvider =>
new DataProtectorTokenFactory<ProviderDeleteTokenable>(
ProviderDeleteTokenable.ClearTextPrefix,
ProviderDeleteTokenable.DataProtectorPurpose,
serviceProvider.GetDataProtectionProvider(),
serviceProvider.GetRequiredService<ILogger<DataProtectorTokenFactory<ProviderDeleteTokenable>>>())
);
}
public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)