1
0
mirror of https://github.com/bitwarden/server synced 2025-12-31 23:53:17 +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>