mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00
[EC-459 / EC-428] Admin panel: Add Provider Type to list and creation flow (#2593)
* [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table * [EC-427] Provider table Type and BillingPhone MySql migrations * [EC-427] Provider table Type and BillingPhone Postgres migrations * [EC-427] Add mysql migration script * [EC-427] Add mysql migration script * [EC-427] Updated Provider sql script to include default column value * [EC-427] Removed default value from Provider.Type column * [EC-427] Changed migration script to include a default value constraint instead of updating the null type * [EC-427] Updated Sql project Provider table script * [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs * [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update] * [EC-427] Adjusting metadata in migration script * [EC-427] Updated Provider sprocs SQL script files * [EC-427] Fixed migration script * [EC-427] Added sqlite migration * [EC-427] Add missing Provider_Update sproc default value * [EC-427] Added missing GO action to migration script * [EC-459] Added Type column to Providers list * [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel * [EC-428] Updated Create Provider view to include new fields * [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller * [EC-428] Added custom validation for Provider fields depending on selected Type * [EC-428] Redirect to Edit after creating Provider * [EC-428] Setting Provider status as Created for Resellers * [EC-428] Redirect on Provider creation depending if self host server * [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync * [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup * [EC-428] Modified Provider views to use DisplayName attribute values * [EC-428] Moved ICreateProviderCommand to Core project * [EC-428] Adding ICreateProviderCommand injection next to IProviderService * [EC-428] Moved CreateProviderCommand to Commercial.Core project * [EC-459] Added Type column to Providers list * [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel * [EC-428] Updated Create Provider view to include new fields * [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller * [EC-428] Added custom validation for Provider fields depending on selected Type * [EC-428] Redirect to Edit after creating Provider * [EC-428] Setting Provider status as Created for Resellers * [EC-428] Redirect on Provider creation depending if self host server * [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync * [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup * [EC-428] Modified Provider views to use DisplayName attribute values * [EC-428] Moved ICreateProviderCommand to Core project * [EC-428] Adding ICreateProviderCommand injection next to IProviderService * [EC-428] Moved CreateProviderCommand to Commercial.Core project * [EC-428] Moved CreateProviderCommand to namespace Bit.Commercial.Core.Providers
This commit is contained in:
@ -0,0 +1,62 @@
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Providers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Commercial.Core.Providers;
|
||||
|
||||
public class CreateProviderCommand : ICreateProviderCommand
|
||||
{
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public CreateProviderCommand(
|
||||
IProviderRepository providerRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IProviderService providerService,
|
||||
IUserRepository userRepository)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_providerService = providerService;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task CreateMspAsync(Provider provider, string ownerEmail)
|
||||
{
|
||||
var owner = await _userRepository.GetByEmailAsync(ownerEmail);
|
||||
if (owner == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid owner. Owner must be an existing Bitwarden user.");
|
||||
}
|
||||
|
||||
await ProviderRepositoryCreateAsync(provider, ProviderStatusType.Pending);
|
||||
|
||||
var providerUser = new ProviderUser
|
||||
{
|
||||
ProviderId = provider.Id,
|
||||
UserId = owner.Id,
|
||||
Type = ProviderUserType.ProviderAdmin,
|
||||
Status = ProviderUserStatusType.Confirmed,
|
||||
};
|
||||
await _providerUserRepository.CreateAsync(providerUser);
|
||||
await _providerService.SendProviderSetupInviteEmailAsync(provider, owner.Email);
|
||||
}
|
||||
|
||||
public async Task CreateResellerAsync(Provider provider)
|
||||
{
|
||||
await ProviderRepositoryCreateAsync(provider, ProviderStatusType.Created);
|
||||
}
|
||||
|
||||
private async Task ProviderRepositoryCreateAsync(Provider provider, ProviderStatusType status)
|
||||
{
|
||||
provider.Status = status;
|
||||
provider.Enabled = true;
|
||||
provider.UseEvents = true;
|
||||
await _providerRepository.CreateAsync(provider);
|
||||
}
|
||||
}
|
@ -53,33 +53,6 @@ public class ProviderService : IProviderService
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task CreateAsync(string ownerEmail)
|
||||
{
|
||||
var owner = await _userRepository.GetByEmailAsync(ownerEmail);
|
||||
if (owner == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid owner. Owner must be an existing Bitwarden user.");
|
||||
}
|
||||
|
||||
var provider = new Provider
|
||||
{
|
||||
Status = ProviderStatusType.Pending,
|
||||
Enabled = true,
|
||||
UseEvents = true,
|
||||
};
|
||||
await _providerRepository.CreateAsync(provider);
|
||||
|
||||
var providerUser = new ProviderUser
|
||||
{
|
||||
ProviderId = provider.Id,
|
||||
UserId = owner.Id,
|
||||
Type = ProviderUserType.ProviderAdmin,
|
||||
Status = ProviderUserStatusType.Confirmed,
|
||||
};
|
||||
await _providerUserRepository.CreateAsync(providerUser);
|
||||
await SendProviderSetupInviteEmailAsync(provider, owner.Email);
|
||||
}
|
||||
|
||||
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key)
|
||||
{
|
||||
var owner = await _userService.GetUserByIdAsync(ownerUserId);
|
||||
@ -456,7 +429,7 @@ public class ProviderService : IProviderService
|
||||
await SendProviderSetupInviteEmailAsync(provider, owner.Email);
|
||||
}
|
||||
|
||||
private async Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail)
|
||||
public async Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail)
|
||||
{
|
||||
var token = _dataProtector.Protect($"ProviderSetupInvite {provider.Id} {ownerEmail} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
||||
await _mailService.SendProviderSetupInviteEmailAsync(provider, token, ownerEmail);
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Commercial.Core.Services;
|
||||
using Bit.Commercial.Core.Providers;
|
||||
using Bit.Commercial.Core.Services;
|
||||
using Bit.Core.Providers.Interfaces;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@ -9,5 +11,6 @@ public static class ServiceCollectionExtensions
|
||||
public static void AddCommercialCoreServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IProviderService, ProviderService>();
|
||||
services.AddScoped<ICreateProviderCommand, CreateProviderCommand>();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
using Bit.Commercial.Core.Providers;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.ProviderFeatures;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateProviderCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateMspAsync_UserIdIsInvalid_Throws(Provider provider, SutProvider<CreateProviderCommand> sutProvider)
|
||||
{
|
||||
provider.Type = ProviderType.Msp;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.CreateMspAsync(provider, default));
|
||||
Assert.Contains("Invalid owner.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateMspAsync_Success(Provider provider, User user, SutProvider<CreateProviderCommand> sutProvider)
|
||||
{
|
||||
provider.Type = ProviderType.Msp;
|
||||
|
||||
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||
userRepository.GetByEmailAsync(user.Email).Returns(user);
|
||||
|
||||
await sutProvider.Sut.CreateMspAsync(provider, user.Email);
|
||||
|
||||
await sutProvider.GetDependency<IProviderRepository>().ReceivedWithAnyArgs().CreateAsync(default);
|
||||
await sutProvider.GetDependency<IProviderService>().Received(1).SendProviderSetupInviteEmailAsync(provider, user.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateResellerAsync_Success(Provider provider, SutProvider<CreateProviderCommand> sutProvider)
|
||||
{
|
||||
provider.Type = ProviderType.Reseller;
|
||||
|
||||
await sutProvider.Sut.CreateResellerAsync(provider);
|
||||
|
||||
await sutProvider.GetDependency<IProviderRepository>().ReceivedWithAnyArgs().CreateAsync(default);
|
||||
await sutProvider.GetDependency<IProviderService>().DidNotReceiveWithAnyArgs().SendProviderSetupInviteEmailAsync(default, default);
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ using Microsoft.AspNetCore.DataProtection;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
using Provider = Bit.Core.Entities.Provider.Provider;
|
||||
using ProviderUser = Bit.Core.Entities.Provider.ProviderUser;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.Services;
|
||||
@ -24,26 +25,6 @@ namespace Bit.Commercial.Core.Test.Services;
|
||||
[SutProviderCustomize]
|
||||
public class ProviderServiceTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_UserIdIsInvalid_Throws(SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.CreateAsync(default));
|
||||
Assert.Contains("Invalid owner.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_Success(User user, SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||
userRepository.GetByEmailAsync(user.Email).Returns(user);
|
||||
|
||||
await sutProvider.Sut.CreateAsync(user.Email);
|
||||
|
||||
await sutProvider.GetDependency<IProviderRepository>().ReceivedWithAnyArgs().CreateAsync(default);
|
||||
await sutProvider.GetDependency<IMailService>().ReceivedWithAnyArgs().SendProviderSetupInviteEmailAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CompleteSetupAsync_UserIdIsInvalid_Throws(SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
@ -229,6 +210,14 @@ public class ProviderServiceTests
|
||||
Assert.True(result.All(r => r.Item2 == ""));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SendProviderSetupInviteEmailAsync_Success(Provider provider, string email, SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.SendProviderSetupInviteEmailAsync(provider, email);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendProviderSetupInviteEmailAsync(provider, Arg.Any<string>(), email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AcceptUserAsync_UserIsInvalid_Throws(SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Providers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -19,10 +21,16 @@ public class ProvidersController : Controller
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly ICreateProviderCommand _createProviderCommand;
|
||||
|
||||
public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService,
|
||||
GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
|
||||
public ProvidersController(
|
||||
IProviderRepository providerRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IProviderService providerService,
|
||||
GlobalSettings globalSettings,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
ICreateProviderCommand createProviderCommand)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
@ -30,6 +38,7 @@ public class ProvidersController : Controller
|
||||
_providerService = providerService;
|
||||
_globalSettings = globalSettings;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_createProviderCommand = createProviderCommand;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
|
||||
@ -75,9 +84,18 @@ public class ProvidersController : Controller
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _providerService.CreateAsync(model.OwnerEmail);
|
||||
var provider = model.ToProvider();
|
||||
switch (provider.Type)
|
||||
{
|
||||
case ProviderType.Msp:
|
||||
await _createProviderCommand.CreateMspAsync(provider, model.OwnerEmail);
|
||||
break;
|
||||
case ProviderType.Reseller:
|
||||
await _createProviderCommand.CreateResellerAsync(provider);
|
||||
break;
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
return RedirectToAction("Edit", new { id = provider.Id });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
|
@ -1,12 +1,59 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.SharedWeb.Utilities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class CreateProviderModel
|
||||
public class CreateProviderModel : IValidatableObject
|
||||
{
|
||||
public CreateProviderModel() { }
|
||||
|
||||
[Display(Name = "Provider Type")]
|
||||
public ProviderType Type { get; set; }
|
||||
|
||||
[Display(Name = "Owner Email")]
|
||||
[Required]
|
||||
public string OwnerEmail { get; set; }
|
||||
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
|
||||
[Display(Name = "Primary Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
|
||||
public virtual Provider ToProvider()
|
||||
{
|
||||
return new Provider()
|
||||
{
|
||||
Type = Type,
|
||||
BusinessName = BusinessName,
|
||||
BillingEmail = BillingEmail?.ToLowerInvariant().Trim()
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case ProviderType.Msp:
|
||||
if (string.IsNullOrWhiteSpace(OwnerEmail))
|
||||
{
|
||||
var ownerEmailDisplayName = nameof(OwnerEmail).GetDisplayAttribute<CreateProviderModel>()?.GetName();
|
||||
yield return new ValidationResult($"The {ownerEmailDisplayName} field is required.");
|
||||
}
|
||||
break;
|
||||
case ProviderType.Reseller:
|
||||
if (string.IsNullOrWhiteSpace(BusinessName))
|
||||
{
|
||||
var businessNameDisplayName = nameof(BusinessName).GetDisplayAttribute<CreateProviderModel>()?.GetName();
|
||||
yield return new ValidationResult($"The {businessNameDisplayName} field is required.");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(BillingEmail))
|
||||
{
|
||||
var billingEmailDisplayName = nameof(BillingEmail).GetDisplayAttribute<CreateProviderModel>()?.GetName();
|
||||
yield return new ValidationResult($"The {billingEmailDisplayName} field is required.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,55 @@
|
||||
@model CreateProviderModel
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model CreateProviderModel
|
||||
@{
|
||||
ViewData["Title"] = "Create Provider";
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function toggleProviderTypeInfo(value) {
|
||||
document.querySelectorAll('[id^="info-"]').forEach(el => { el.classList.add('d-none'); });
|
||||
document.getElementById('info-' + value).classList.remove('d-none');
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
<h1>Create Provider</h1>
|
||||
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="OwnerEmail"></label>
|
||||
<input type="text" class="form-control" asp-for="OwnerEmail">
|
||||
<label asp-for="Type" class="h2"></label>
|
||||
@foreach(ProviderType providerType in Enum.GetValues(typeof(ProviderType)))
|
||||
{
|
||||
var providerTypeValue = (int)providerType;
|
||||
<div class="form-check">
|
||||
@Html.RadioButtonFor(m => m.Type, providerType, new { id = $"providerType-{providerTypeValue}", @class = "form-check-input", onclick=$"toggleProviderTypeInfo({providerTypeValue})" })
|
||||
@Html.LabelFor(m => m.Type, providerType.GetDisplayAttribute()?.GetName(), new { @class = "form-check-label align-middle", @for = $"providerType-{providerTypeValue}" })
|
||||
<br/>
|
||||
@Html.LabelFor(m => m.Type, providerType.GetDisplayAttribute()?.GetDescription(), new { @class = "form-check-label small text-muted ml-3 align-top", @for = $"providerType-{providerTypeValue}" })
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="@($"info-{(int)ProviderType.Msp}")" class="form-group @(Model.Type != ProviderType.Msp ? "d-none" : string.Empty)">
|
||||
<h2>MSP Info</h2>
|
||||
<div class="form-group">
|
||||
<label asp-for="OwnerEmail"></label>
|
||||
<input type="text" class="form-control" asp-for="OwnerEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="@($"info-{(int)ProviderType.Reseller}")" class="form-group @(Model.Type != ProviderType.Reseller ? "d-none" : string.Empty)">
|
||||
<h2>Reseller Info</h2>
|
||||
<div class="form-group">
|
||||
<label asp-for="BusinessName"></label>
|
||||
<input type="text" class="form-control" asp-for="BusinessName">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="BillingEmail"></label>
|
||||
<input type="text" class="form-control" asp-for="BillingEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-2">Create Provider</button>
|
||||
|
@ -1,4 +1,5 @@
|
||||
@model ProvidersModel
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model ProvidersModel
|
||||
@{
|
||||
ViewData["Title"] = "Providers";
|
||||
}
|
||||
@ -25,6 +26,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="width: 190px;">Provider Type</th>
|
||||
<th style="width: 190px;">Status</th>
|
||||
<th style="width: 150px;">Created</th>
|
||||
</tr>
|
||||
@ -44,6 +46,7 @@
|
||||
<td>
|
||||
<a asp-action="@Model.Action" asp-route-id="@provider.Id">@(provider.Name ?? "Pending")</a>
|
||||
</td>
|
||||
<td>@provider.Type.GetDisplayAttribute()?.GetShortName()</td>
|
||||
<td>@provider.Status</td>
|
||||
<td>
|
||||
<span title="@provider.CreationDate.ToString()">
|
||||
|
@ -1,7 +1,11 @@
|
||||
namespace Bit.Core.Enums.Provider;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Enums.Provider;
|
||||
|
||||
public enum ProviderType : byte
|
||||
{
|
||||
[Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Access to clients organization")]
|
||||
Msp = 0,
|
||||
[Display(ShortName = "Reseller", Name = "Reseller", Description = "Access to clients billing")]
|
||||
Reseller = 1,
|
||||
}
|
||||
|
9
src/Core/Providers/Interfaces/ICreateProviderCommand.cs
Normal file
9
src/Core/Providers/Interfaces/ICreateProviderCommand.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Bit.Core.Entities.Provider;
|
||||
|
||||
namespace Bit.Core.Providers.Interfaces;
|
||||
|
||||
public interface ICreateProviderCommand
|
||||
{
|
||||
Task CreateMspAsync(Provider provider, string ownerEmail);
|
||||
Task CreateResellerAsync(Provider provider);
|
||||
}
|
@ -7,7 +7,6 @@ namespace Bit.Core.Services;
|
||||
|
||||
public interface IProviderService
|
||||
{
|
||||
Task CreateAsync(string ownerEmail);
|
||||
Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key);
|
||||
Task UpdateAsync(Provider provider, bool updateBilling = false);
|
||||
|
||||
@ -26,5 +25,6 @@ public interface IProviderService
|
||||
Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId);
|
||||
Task LogProviderAccessToOrganizationAsync(Guid organizationId);
|
||||
Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId);
|
||||
Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail);
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,6 @@ namespace Bit.Core.Services;
|
||||
|
||||
public class NoopProviderService : IProviderService
|
||||
{
|
||||
public Task CreateAsync(string ownerEmail) => throw new NotImplementedException();
|
||||
|
||||
public Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) => throw new NotImplementedException();
|
||||
|
||||
public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException();
|
||||
@ -34,4 +32,5 @@ public class NoopProviderService : IProviderService
|
||||
public Task LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException();
|
||||
|
||||
public Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid userId) => throw new NotImplementedException();
|
||||
public Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail) => throw new NotImplementedException();
|
||||
}
|
||||
|
21
src/SharedWeb/Utilities/DisplayAttributeHelpers.cs
Normal file
21
src/SharedWeb/Utilities/DisplayAttributeHelpers.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Bit.SharedWeb.Utilities;
|
||||
|
||||
public static class DisplayAttributeHelpers
|
||||
{
|
||||
public static DisplayAttribute GetDisplayAttribute(this Enum enumValue)
|
||||
{
|
||||
return enumValue.GetType()
|
||||
.GetMember(enumValue.ToString())
|
||||
.First()
|
||||
.GetCustomAttribute<DisplayAttribute>();
|
||||
}
|
||||
|
||||
public static DisplayAttribute GetDisplayAttribute<T>(this string property)
|
||||
{
|
||||
MemberInfo propertyInfo = typeof(T).GetProperty(property);
|
||||
return propertyInfo?.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user