1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

[EC-430] Admin portal: Update organization information screen (#2672)

* [EC-430] Added ProviderOrganizationProviderDetailsView to get Provider details for an Organization

* [EC-430] Added Provider information to Organization Edit/View on Admin panel

* [EC-430] Remove "Add to Reseller" button

* [EC-430] Removed unused property OrganizationEditModel.ClientOwnerEmail

* [EC-430] Replaced IProviderOrganizationRepository.GetProviderDetailsByOrganizationAsync with IProviderRepository.GetByOrganizationIdAsync

* [EC-430] Deleted ProviderOrganizationProviderDetails and ProviderOrganizationProviderDetailsReadByOrganizationIdQuery
This commit is contained in:
Rui Tomé
2023-02-13 10:26:58 +00:00
committed by GitHub
parent f331188763
commit 624df49698
12 changed files with 102 additions and 7 deletions

View File

@ -30,6 +30,7 @@ public class OrganizationsController : Controller
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IProviderRepository _providerRepository;
private readonly ILogger<OrganizationsController> _logger; private readonly ILogger<OrganizationsController> _logger;
public OrganizationsController( public OrganizationsController(
@ -47,6 +48,7 @@ public class OrganizationsController : Controller
GlobalSettings globalSettings, GlobalSettings globalSettings,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
IUserService userService, IUserService userService,
IProviderRepository providerRepository,
ILogger<OrganizationsController> logger) ILogger<OrganizationsController> logger)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -63,6 +65,7 @@ public class OrganizationsController : Controller
_globalSettings = globalSettings; _globalSettings = globalSettings;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_userService = userService; _userService = userService;
_providerRepository = providerRepository;
_logger = logger; _logger = logger;
} }
@ -102,6 +105,7 @@ public class OrganizationsController : Controller
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
var provider = await _providerRepository.GetByOrganizationIdAsync(id);
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id); var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id); var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
IEnumerable<Group> groups = null; IEnumerable<Group> groups = null;
@ -116,7 +120,7 @@ public class OrganizationsController : Controller
} }
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id); var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null; var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
return View(new OrganizationViewModel(organization, billingSyncConnection, users, ciphers, collections, groups, policies)); return View(new OrganizationViewModel(organization, provider, billingSyncConnection, users, ciphers, collections, groups, policies));
} }
[SelfHosted(NotSelfHostedOnly = true)] [SelfHosted(NotSelfHostedOnly = true)]
@ -128,6 +132,7 @@ public class OrganizationsController : Controller
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
var provider = await _providerRepository.GetByOrganizationIdAsync(id);
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id); var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id); var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
IEnumerable<Group> groups = null; IEnumerable<Group> groups = null;
@ -143,7 +148,7 @@ public class OrganizationsController : Controller
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id); var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
var billingInfo = await _paymentService.GetBillingAsync(organization); var billingInfo = await _paymentService.GetBillingAsync(organization);
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null; var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
return View(new OrganizationEditModel(organization, users, ciphers, collections, groups, policies, return View(new OrganizationEditModel(organization, provider, users, ciphers, collections, groups, policies,
billingInfo, billingSyncConnection, _globalSettings)); billingInfo, billingSyncConnection, _globalSettings));
} }

View File

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
@ -12,11 +13,11 @@ public class OrganizationEditModel : OrganizationViewModel
{ {
public OrganizationEditModel() { } public OrganizationEditModel() { }
public OrganizationEditModel(Organization org, IEnumerable<OrganizationUserUserDetails> orgUsers, public OrganizationEditModel(Organization org, Provider provider, IEnumerable<OrganizationUserUserDetails> orgUsers,
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections, IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
GlobalSettings globalSettings) GlobalSettings globalSettings)
: base(org, connections, orgUsers, ciphers, collections, groups, policies) : base(org, provider, connections, orgUsers, ciphers, collections, groups, policies)
{ {
BillingInfo = billingInfo; BillingInfo = billingInfo;
BraintreeMerchantId = globalSettings.Braintree.MerchantId; BraintreeMerchantId = globalSettings.Braintree.MerchantId;
@ -59,7 +60,7 @@ public class OrganizationEditModel : OrganizationViewModel
public string BraintreeMerchantId { get; set; } public string BraintreeMerchantId { get; set; }
[Required] [Required]
[Display(Name = "Name")] [Display(Name = "Organization Name")]
public string Name { get; set; } public string Name { get; set; }
[Display(Name = "Business Name")] [Display(Name = "Business Name")]
public string BusinessName { get; set; } public string BusinessName { get; set; }

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
@ -8,11 +9,12 @@ public class OrganizationViewModel
{ {
public OrganizationViewModel() { } public OrganizationViewModel() { }
public OrganizationViewModel(Organization org, IEnumerable<OrganizationConnection> connections, public OrganizationViewModel(Organization org, Provider provider, IEnumerable<OrganizationConnection> connections,
IEnumerable<OrganizationUserUserDetails> orgUsers, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<OrganizationUserUserDetails> orgUsers, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
IEnumerable<Group> groups, IEnumerable<Policy> policies) IEnumerable<Group> groups, IEnumerable<Policy> policies)
{ {
Organization = org; Organization = org;
Provider = provider;
Connections = connections ?? Enumerable.Empty<OrganizationConnection>(); Connections = connections ?? Enumerable.Empty<OrganizationConnection>();
HasPublicPrivateKeys = org.PublicKey != null && org.PrivateKey != null; HasPublicPrivateKeys = org.PublicKey != null && org.PrivateKey != null;
UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited); UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited);
@ -34,6 +36,7 @@ public class OrganizationViewModel
} }
public Organization Organization { get; set; } public Organization Organization { get; set; }
public Provider Provider { get; set; }
public IEnumerable<OrganizationConnection> Connections { get; set; } public IEnumerable<OrganizationConnection> Connections { get; set; }
public string Owners { get; set; } public string Owners { get; set; }
public string Admins { get; set; } public string Admins { get; set; }

View File

@ -117,6 +117,11 @@
<h1>Organization <small>@Model.Organization.Name</small></h1> <h1>Organization <small>@Model.Organization.Name</small></h1>
@if (Model.Provider != null)
{
<h2>Provider Relationship</h2>
@await Html.PartialAsync("_ProviderInformation", Model.Provider)
}
<h2>Organization Information</h2> <h2>Organization Information</h2>
@await Html.PartialAsync("_ViewInformation", Model) @await Html.PartialAsync("_ViewInformation", Model)
<h2>Billing Information</h2> <h2>Billing Information</h2>
@ -283,7 +288,14 @@
<div class="col-sm"> <div class="col-sm">
<div class="form-group"> <div class="form-group">
<label asp-for="BillingEmail"></label> <label asp-for="BillingEmail"></label>
@if (Model.Provider?.Type == ProviderType.Reseller)
{
<input type="email" class="form-control" asp-for="Provider.BillingEmail" readonly="readonly">
}
else
{
<input type="email" class="form-control" asp-for="BillingEmail"> <input type="email" class="form-control" asp-for="BillingEmail">
}
</div> </div>
</div> </div>
<div class="col-sm"> <div class="col-sm">

View File

@ -6,6 +6,11 @@
<h1>Organization <small>@Model.Organization.Name</small></h1> <h1>Organization <small>@Model.Organization.Name</small></h1>
@if (Model.Provider != null)
{
<h2>Provider Relationship</h2>
@await Html.PartialAsync("_ProviderInformation", Model.Provider)
}
<h2>Information</h2> <h2>Information</h2>
@await Html.PartialAsync("_ViewInformation", Model) @await Html.PartialAsync("_ViewInformation", Model)
@if(GlobalSettings.SelfHosted) @if(GlobalSettings.SelfHosted)

View File

@ -0,0 +1,9 @@
@using Bit.SharedWeb.Utilities
@model Bit.Core.Entities.Provider.Provider
<dl class="row">
<dt class="col-sm-4 col-lg-3">Provider Name</dt>
<dd class="col-sm-8 col-lg-9">@Model.Name</dd>
<dt class="col-sm-4 col-lg-3">Provider Type</dt>
<dd class="col-sm-8 col-lg-9">@(Model.Type.GetDisplayAttribute()?.GetName())</dd>
</dl>

View File

@ -5,6 +5,7 @@ namespace Bit.Core.Repositories;
public interface IProviderRepository : IRepository<Provider, Guid> public interface IProviderRepository : IRepository<Provider, Guid>
{ {
Task<Provider> GetByOrganizationIdAsync(Guid organizationId);
Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take); Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take);
Task<ICollection<ProviderAbility>> GetManyAbilitiesAsync(); Task<ICollection<ProviderAbility>> GetManyAbilitiesAsync();
} }

View File

@ -18,6 +18,19 @@ public class ProviderRepository : Repository<Provider, Guid>, IProviderRepositor
: base(connectionString, readOnlyConnectionString) : base(connectionString, readOnlyConnectionString)
{ } { }
public async Task<Provider> GetByOrganizationIdAsync(Guid organizationId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<Provider>(
"[dbo].[Provider_ReadByOrganizationId]",
new { OrganizationId = organizationId },
commandType: CommandType.StoredProcedure);
return results.FirstOrDefault();
}
}
public async Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take) public async Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take)
{ {
using (var connection = new SqlConnection(ReadOnlyConnectionString)) using (var connection = new SqlConnection(ReadOnlyConnectionString))

View File

@ -25,6 +25,20 @@ public class ProviderRepository : Repository<Provider, Models.Provider, Guid>, I
await base.DeleteAsync(provider); await base.DeleteAsync(provider);
} }
public async Task<Provider> GetByOrganizationIdAsync(Guid organizationId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = from p in dbContext.Providers
join po in dbContext.ProviderOrganizations
on p.Id equals po.ProviderId
where po.OrganizationId == organizationId
select p;
return await query.FirstOrDefaultAsync();
}
}
public async Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take) public async Task<ICollection<Provider>> SearchAsync(string name, string userEmail, int skip, int take)
{ {
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())

View File

@ -282,6 +282,7 @@
<Build Include="dbo\Stored Procedures\Policy_ReadByUserId.sql" /> <Build Include="dbo\Stored Procedures\Policy_ReadByUserId.sql" />
<Build Include="dbo\Stored Procedures\Policy_Update.sql" /> <Build Include="dbo\Stored Procedures\Policy_Update.sql" />
<Build Include="dbo\Stored Procedures\ProviderOrganizationOrganizationDetails_ReadByProviderId.sql" /> <Build Include="dbo\Stored Procedures\ProviderOrganizationOrganizationDetails_ReadByProviderId.sql" />
<Build Include="dbo\Stored Procedures\Provider_ReadByOrganizationId.sql" />
<Build Include="dbo\Stored Procedures\ProviderOrganization_Create.sql" /> <Build Include="dbo\Stored Procedures\ProviderOrganization_Create.sql" />
<Build Include="dbo\Stored Procedures\ProviderOrganization_DeleteById.sql" /> <Build Include="dbo\Stored Procedures\ProviderOrganization_DeleteById.sql" />
<Build Include="dbo\Stored Procedures\ProviderOrganization_ReadById.sql" /> <Build Include="dbo\Stored Procedures\ProviderOrganization_ReadById.sql" />

View File

@ -0,0 +1,15 @@
CREATE PROCEDURE [dbo].[Provider_ReadByOrganizationId]
@OrganizationId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
P.*
FROM
[dbo].[ProviderView] P
INNER JOIN
[dbo].[ProviderOrganization] PO ON PO.[ProviderId] = P.[Id]
WHERE
PO.[OrganizationId] = @OrganizationId
END

View File

@ -0,0 +1,16 @@
CREATE OR ALTER PROCEDURE [dbo].[Provider_ReadByOrganizationId]
@OrganizationId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
P.*
FROM
[dbo].[ProviderView] P
INNER JOIN
[dbo].[ProviderOrganization] PO ON PO.[ProviderId] = P.[Id]
WHERE
PO.[OrganizationId] = @OrganizationId
END
GO