mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 00:22:50 -05:00
[AC-434] Hide Billing screen for Reseller clients (#2783)
* [AC-434] Added ProviderType to ProfileOrganizationResponseModel * [AC-434] Migration script * [AC-434] Fixed indentation on migration script * [AC-434] Hiding sensitive subscription data if the user does not have permissions * [AC-434] Fixed missing dependency in unit test * [AC-434] Altered BillingSubscription.Amount and BillingSubscriptionUpcomingInvoice.Amount to nullable * [AC-434] Replaced CurrentContext.ManageBilling with ViewBillingHistory, ViewSubscription, EditSubscription and EditPaymentMethods * [AC-434] Reverted change on BillingSubscription.Amount and now setting Subscription.Items = null when User does not have permission * [AC-434] Added ProviderOrganizationProviderDetails_ReadByUserId * [AC-434] Added IProviderOrganizationRepository.GetManyByUserAsync * [AC-434] Added CurrentContext.GetOrganizationProviderDetails * [AC-434] Remove unneeded join Organization table
This commit is contained in:
@ -28,6 +28,7 @@ public class OrganizationsController : Controller
|
|||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IPolicyRepository _policyRepository;
|
private readonly IPolicyRepository _policyRepository;
|
||||||
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IPaymentService _paymentService;
|
private readonly IPaymentService _paymentService;
|
||||||
@ -46,6 +47,7 @@ public class OrganizationsController : Controller
|
|||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
|
IProviderRepository providerRepository,
|
||||||
IOrganizationService organizationService,
|
IOrganizationService organizationService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
@ -63,6 +65,7 @@ public class OrganizationsController : Controller
|
|||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_policyRepository = policyRepository;
|
_policyRepository = policyRepository;
|
||||||
|
_providerRepository = providerRepository;
|
||||||
_organizationService = organizationService;
|
_organizationService = organizationService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_paymentService = paymentService;
|
_paymentService = paymentService;
|
||||||
@ -101,7 +104,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task<BillingResponseModel> GetBilling(string id)
|
public async Task<BillingResponseModel> GetBilling(string id)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.ViewBillingHistory(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -120,7 +123,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task<OrganizationSubscriptionResponseModel> GetSubscription(string id)
|
public async Task<OrganizationSubscriptionResponseModel> GetSubscription(string id)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.ViewSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -139,7 +142,9 @@ public class OrganizationsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo);
|
var hideSensitiveData = !await _currentContext.EditSubscription(orgIdGuid);
|
||||||
|
|
||||||
|
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -240,7 +245,7 @@ public class OrganizationsController : Controller
|
|||||||
model.BillingEmail != organization.BillingEmail);
|
model.BillingEmail != organization.BillingEmail);
|
||||||
|
|
||||||
var hasRequiredPermissions = updateBilling
|
var hasRequiredPermissions = updateBilling
|
||||||
? await _currentContext.ManageBilling(orgIdGuid)
|
? await _currentContext.EditSubscription(orgIdGuid)
|
||||||
: await _currentContext.OrganizationOwner(orgIdGuid);
|
: await _currentContext.OrganizationOwner(orgIdGuid);
|
||||||
|
|
||||||
if (!hasRequiredPermissions)
|
if (!hasRequiredPermissions)
|
||||||
@ -257,7 +262,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task PostPayment(string id, [FromBody] PaymentRequestModel model)
|
public async Task PostPayment(string id, [FromBody] PaymentRequestModel model)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditPaymentMethods(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -280,7 +285,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody] OrganizationUpgradeRequestModel model)
|
public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody] OrganizationUpgradeRequestModel model)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -294,7 +299,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task PostSubscription(string id, [FromBody] OrganizationSubscriptionUpdateRequestModel model)
|
public async Task PostSubscription(string id, [FromBody] OrganizationSubscriptionUpdateRequestModel model)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -307,7 +312,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody] OrganizationSeatRequestModel model)
|
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody] OrganizationSeatRequestModel model)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -321,7 +326,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task<PaymentResponseModel> PostStorage(string id, [FromBody] StorageRequestModel model)
|
public async Task<PaymentResponseModel> PostStorage(string id, [FromBody] StorageRequestModel model)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -335,7 +340,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task PostVerifyBank(string id, [FromBody] OrganizationVerifyBankRequestModel model)
|
public async Task PostVerifyBank(string id, [FromBody] OrganizationVerifyBankRequestModel model)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -348,7 +353,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task PostCancel(string id)
|
public async Task PostCancel(string id)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -361,7 +366,7 @@ public class OrganizationsController : Controller
|
|||||||
public async Task PostReinstate(string id)
|
public async Task PostReinstate(string id)
|
||||||
{
|
{
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManageBilling(orgIdGuid))
|
if (!await _currentContext.EditSubscription(orgIdGuid))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
@ -84,28 +84,30 @@ public class OrganizationResponseModel : ResponseModel
|
|||||||
|
|
||||||
public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||||
{
|
{
|
||||||
public OrganizationSubscriptionResponseModel(Organization organization, SubscriptionInfo subscription = null)
|
public OrganizationSubscriptionResponseModel(Organization organization) : base(organization, "organizationSubscription")
|
||||||
: base(organization, "organizationSubscription")
|
|
||||||
{
|
|
||||||
if (subscription != null)
|
|
||||||
{
|
|
||||||
Subscription = subscription.Subscription != null ?
|
|
||||||
new BillingSubscription(subscription.Subscription) : null;
|
|
||||||
UpcomingInvoice = subscription.UpcomingInvoice != null ?
|
|
||||||
new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
|
|
||||||
Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Expiration = organization.ExpirationDate;
|
Expiration = organization.ExpirationDate;
|
||||||
}
|
|
||||||
|
|
||||||
StorageName = organization.Storage.HasValue ?
|
StorageName = organization.Storage.HasValue ?
|
||||||
CoreHelpers.ReadableBytesSize(organization.Storage.Value) : null;
|
CoreHelpers.ReadableBytesSize(organization.Storage.Value) : null;
|
||||||
StorageGb = organization.Storage.HasValue ?
|
StorageGb = organization.Storage.HasValue ?
|
||||||
Math.Round(organization.Storage.Value / 1073741824D, 2) : 0; // 1 GB
|
Math.Round(organization.Storage.Value / 1073741824D, 2) : 0; // 1 GB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OrganizationSubscriptionResponseModel(Organization organization, SubscriptionInfo subscription, bool hideSensitiveData)
|
||||||
|
: this(organization)
|
||||||
|
{
|
||||||
|
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
|
||||||
|
UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
|
||||||
|
Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value.
|
||||||
|
|
||||||
|
if (hideSensitiveData)
|
||||||
|
{
|
||||||
|
BillingEmail = null;
|
||||||
|
Subscription.Items = null;
|
||||||
|
UpcomingInvoice.Amount = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string StorageName { get; set; }
|
public string StorageName { get; set; }
|
||||||
public double? StorageGb { get; set; }
|
public double? StorageGb { get; set; }
|
||||||
public BillingSubscription Subscription { get; set; }
|
public BillingSubscription Subscription { get; set; }
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Enums.Provider;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
@ -46,6 +47,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
UserId = organization.UserId?.ToString();
|
UserId = organization.UserId?.ToString();
|
||||||
ProviderId = organization.ProviderId?.ToString();
|
ProviderId = organization.ProviderId?.ToString();
|
||||||
ProviderName = organization.ProviderName;
|
ProviderName = organization.ProviderName;
|
||||||
|
ProviderType = organization.ProviderType;
|
||||||
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
|
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
|
||||||
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
|
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
|
||||||
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
|
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
|
||||||
@ -97,6 +99,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
public bool HasPublicAndPrivateKeys { get; set; }
|
public bool HasPublicAndPrivateKeys { get; set; }
|
||||||
public string ProviderId { get; set; }
|
public string ProviderId { get; set; }
|
||||||
public string ProviderName { get; set; }
|
public string ProviderName { get; set; }
|
||||||
|
public ProviderType? ProviderType { get; set; }
|
||||||
public string FamilySponsorshipFriendlyName { get; set; }
|
public string FamilySponsorshipFriendlyName { get; set; }
|
||||||
public bool FamilySponsorshipAvailable { get; set; }
|
public bool FamilySponsorshipAvailable { get; set; }
|
||||||
public ProductType PlanProductType { get; set; }
|
public ProductType PlanProductType { get; set; }
|
||||||
|
@ -100,6 +100,6 @@ public class BillingSubscriptionUpcomingInvoice
|
|||||||
Date = inv.Date;
|
Date = inv.Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
public decimal Amount { get; set; }
|
public decimal? Amount { get; set; }
|
||||||
public DateTime? Date { get; set; }
|
public DateTime? Date { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@ namespace Bit.Core.Context;
|
|||||||
|
|
||||||
public class CurrentContext : ICurrentContext
|
public class CurrentContext : ICurrentContext
|
||||||
{
|
{
|
||||||
|
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||||
private readonly IProviderUserRepository _providerUserRepository;
|
private readonly IProviderUserRepository _providerUserRepository;
|
||||||
private bool _builtHttpContext;
|
private bool _builtHttpContext;
|
||||||
private bool _builtClaimsPrincipal;
|
private bool _builtClaimsPrincipal;
|
||||||
|
private IEnumerable<ProviderOrganizationProviderDetails> _providerOrganizationProviderDetails;
|
||||||
private IEnumerable<ProviderUserOrganizationDetails> _providerUserOrganizations;
|
private IEnumerable<ProviderUserOrganizationDetails> _providerUserOrganizations;
|
||||||
|
|
||||||
public virtual HttpContext HttpContext { get; set; }
|
public virtual HttpContext HttpContext { get; set; }
|
||||||
@ -37,8 +39,11 @@ public class CurrentContext : ICurrentContext
|
|||||||
public virtual ClientType ClientType { get; set; }
|
public virtual ClientType ClientType { get; set; }
|
||||||
public virtual Guid? ServiceAccountOrganizationId { get; set; }
|
public virtual Guid? ServiceAccountOrganizationId { get; set; }
|
||||||
|
|
||||||
public CurrentContext(IProviderUserRepository providerUserRepository)
|
public CurrentContext(
|
||||||
|
IProviderOrganizationRepository providerOrganizationRepository,
|
||||||
|
IProviderUserRepository providerUserRepository)
|
||||||
{
|
{
|
||||||
|
_providerOrganizationRepository = providerOrganizationRepository;
|
||||||
_providerUserRepository = providerUserRepository;
|
_providerUserRepository = providerUserRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,15 +397,34 @@ public class CurrentContext : ICurrentContext
|
|||||||
&& (o.Permissions?.ManageResetPassword ?? false)) ?? false);
|
&& (o.Permissions?.ManageResetPassword ?? false)) ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ManageBilling(Guid orgId)
|
public async Task<bool> ViewSubscription(Guid orgId)
|
||||||
{
|
{
|
||||||
var orgManagedByProvider = await ProviderIdForOrg(orgId) != null;
|
var orgManagedByMspProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId && po.ProviderType == ProviderType.Msp);
|
||||||
|
|
||||||
|
return orgManagedByMspProvider
|
||||||
|
? await ProviderUserForOrgAsync(orgId)
|
||||||
|
: await OrganizationOwner(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EditSubscription(Guid orgId)
|
||||||
|
{
|
||||||
|
var orgManagedByProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId);
|
||||||
|
|
||||||
return orgManagedByProvider
|
return orgManagedByProvider
|
||||||
? await ProviderUserForOrgAsync(orgId)
|
? await ProviderUserForOrgAsync(orgId)
|
||||||
: await OrganizationOwner(orgId);
|
: await OrganizationOwner(orgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EditPaymentMethods(Guid orgId)
|
||||||
|
{
|
||||||
|
return await EditSubscription(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ViewBillingHistory(Guid orgId)
|
||||||
|
{
|
||||||
|
return await EditSubscription(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
public bool ProviderProviderAdmin(Guid providerId)
|
public bool ProviderProviderAdmin(Guid providerId)
|
||||||
{
|
{
|
||||||
return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false;
|
return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false;
|
||||||
@ -433,7 +457,7 @@ public class CurrentContext : ICurrentContext
|
|||||||
|
|
||||||
public async Task<bool> ProviderUserForOrgAsync(Guid orgId)
|
public async Task<bool> ProviderUserForOrgAsync(Guid orgId)
|
||||||
{
|
{
|
||||||
return (await GetProviderOrganizations()).Any(po => po.OrganizationId == orgId);
|
return (await GetProviderUserOrganizations()).Any(po => po.OrganizationId == orgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Guid?> ProviderIdForOrg(Guid orgId)
|
public async Task<Guid?> ProviderIdForOrg(Guid orgId)
|
||||||
@ -443,7 +467,7 @@ public class CurrentContext : ICurrentContext
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var po = (await GetProviderOrganizations())
|
var po = (await GetProviderUserOrganizations())
|
||||||
?.FirstOrDefault(po => po.OrganizationId == orgId);
|
?.FirstOrDefault(po => po.OrganizationId == orgId);
|
||||||
|
|
||||||
return po?.ProviderId;
|
return po?.ProviderId;
|
||||||
@ -520,7 +544,7 @@ public class CurrentContext : ICurrentContext
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<IEnumerable<ProviderUserOrganizationDetails>> GetProviderOrganizations()
|
protected async Task<IEnumerable<ProviderUserOrganizationDetails>> GetProviderUserOrganizations()
|
||||||
{
|
{
|
||||||
if (_providerUserOrganizations == null && UserId.HasValue)
|
if (_providerUserOrganizations == null && UserId.HasValue)
|
||||||
{
|
{
|
||||||
@ -529,4 +553,14 @@ public class CurrentContext : ICurrentContext
|
|||||||
|
|
||||||
return _providerUserOrganizations;
|
return _providerUserOrganizations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async Task<IEnumerable<ProviderOrganizationProviderDetails>> GetOrganizationProviderDetails()
|
||||||
|
{
|
||||||
|
if (_providerOrganizationProviderDetails == null && UserId.HasValue)
|
||||||
|
{
|
||||||
|
_providerOrganizationProviderDetails = await _providerOrganizationRepository.GetManyByUserAsync(UserId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _providerOrganizationProviderDetails;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,10 @@ public interface ICurrentContext
|
|||||||
Task<bool> ManageUsers(Guid orgId);
|
Task<bool> ManageUsers(Guid orgId);
|
||||||
Task<bool> ManageScim(Guid orgId);
|
Task<bool> ManageScim(Guid orgId);
|
||||||
Task<bool> ManageResetPassword(Guid orgId);
|
Task<bool> ManageResetPassword(Guid orgId);
|
||||||
Task<bool> ManageBilling(Guid orgId);
|
Task<bool> ViewSubscription(Guid orgId);
|
||||||
|
Task<bool> EditSubscription(Guid orgId);
|
||||||
|
Task<bool> EditPaymentMethods(Guid orgId);
|
||||||
|
Task<bool> ViewBillingHistory(Guid orgId);
|
||||||
Task<bool> ProviderUserForOrgAsync(Guid orgId);
|
Task<bool> ProviderUserForOrgAsync(Guid orgId);
|
||||||
bool ProviderProviderAdmin(Guid providerId);
|
bool ProviderProviderAdmin(Guid providerId);
|
||||||
bool ProviderUser(Guid providerId);
|
bool ProviderUser(Guid providerId);
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Enums.Provider;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
|
|
||||||
public class OrganizationUserOrganizationDetails
|
public class OrganizationUserOrganizationDetails
|
||||||
{
|
{
|
||||||
@ -36,6 +38,7 @@ public class OrganizationUserOrganizationDetails
|
|||||||
public string PrivateKey { get; set; }
|
public string PrivateKey { get; set; }
|
||||||
public Guid? ProviderId { get; set; }
|
public Guid? ProviderId { get; set; }
|
||||||
public string ProviderName { get; set; }
|
public string ProviderName { get; set; }
|
||||||
|
public ProviderType? ProviderType { get; set; }
|
||||||
public string FamilySponsorshipFriendlyName { get; set; }
|
public string FamilySponsorshipFriendlyName { get; set; }
|
||||||
public string SsoConfig { get; set; }
|
public string SsoConfig { get; set; }
|
||||||
public DateTime? FamilySponsorshipLastSyncDate { get; set; }
|
public DateTime? FamilySponsorshipLastSyncDate { get; set; }
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
using Bit.Core.Enums.Provider;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Data;
|
||||||
|
|
||||||
|
public class ProviderOrganizationProviderDetails
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid ProviderId { get; set; }
|
||||||
|
public Guid OrganizationId { get; set; }
|
||||||
|
public string ProviderName { get; set; }
|
||||||
|
public ProviderType ProviderType { get; set; }
|
||||||
|
}
|
@ -8,4 +8,5 @@ public interface IProviderOrganizationRepository : IRepository<ProviderOrganizat
|
|||||||
Task<ICollection<ProviderOrganization>> CreateManyAsync(IEnumerable<ProviderOrganization> providerOrganizations);
|
Task<ICollection<ProviderOrganization>> CreateManyAsync(IEnumerable<ProviderOrganization> providerOrganizations);
|
||||||
Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId);
|
Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId);
|
||||||
Task<ProviderOrganization> GetByOrganizationId(Guid organizationId);
|
Task<ProviderOrganization> GetByOrganizationId(Guid organizationId);
|
||||||
|
Task<IEnumerable<ProviderOrganizationProviderDetails>> GetManyByUserAsync(Guid userId);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,19 @@ public class ProviderOrganizationRepository : Repository<ProviderOrganization, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProviderOrganizationProviderDetails>> GetManyByUserAsync(Guid userId)
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<ProviderOrganizationProviderDetails>(
|
||||||
|
"[dbo].[ProviderOrganizationProviderDetails_ReadByUserId]",
|
||||||
|
new { UserId = userId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private DataTable BuildProviderOrganizationsTable(SqlBulkCopy bulkCopy, IEnumerable<ProviderOrganization> providerOrganizations)
|
private DataTable BuildProviderOrganizationsTable(SqlBulkCopy bulkCopy, IEnumerable<ProviderOrganization> providerOrganizations)
|
||||||
{
|
{
|
||||||
var po = providerOrganizations.FirstOrDefault();
|
var po = providerOrganizations.FirstOrDefault();
|
||||||
|
@ -56,4 +56,15 @@ public class ProviderOrganizationRepository :
|
|||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
return await GetDbSet(dbContext).Where(po => po.OrganizationId == organizationId).FirstOrDefaultAsync();
|
return await GetDbSet(dbContext).Where(po => po.OrganizationId == organizationId).FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProviderOrganizationProviderDetails>> GetManyByUserAsync(Guid userId)
|
||||||
|
{
|
||||||
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
var query = new ProviderOrganizationReadByUserIdQuery(userId);
|
||||||
|
var data = await query.Run(dbContext).ToListAsync();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
|
|||||||
Permissions = ou.Permissions,
|
Permissions = ou.Permissions,
|
||||||
ProviderId = p.Id,
|
ProviderId = p.Id,
|
||||||
ProviderName = p.Name,
|
ProviderName = p.Name,
|
||||||
|
ProviderType = p.Type,
|
||||||
SsoConfig = ss.Data,
|
SsoConfig = ss.Data,
|
||||||
FamilySponsorshipFriendlyName = os.FriendlyName,
|
FamilySponsorshipFriendlyName = os.FriendlyName,
|
||||||
FamilySponsorshipLastSyncDate = os.LastSyncDate,
|
FamilySponsorshipLastSyncDate = os.LastSyncDate,
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
||||||
|
|
||||||
|
public class ProviderOrganizationReadByUserIdQuery : IQuery<ProviderOrganizationProviderDetails>
|
||||||
|
{
|
||||||
|
private readonly Guid _userId;
|
||||||
|
|
||||||
|
public ProviderOrganizationReadByUserIdQuery(Guid userId)
|
||||||
|
{
|
||||||
|
_userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<ProviderOrganizationProviderDetails> Run(DatabaseContext dbContext)
|
||||||
|
{
|
||||||
|
var query = from po in dbContext.ProviderOrganizations
|
||||||
|
join ou in dbContext.OrganizationUsers
|
||||||
|
on po.OrganizationId equals ou.OrganizationId
|
||||||
|
join p in dbContext.Providers
|
||||||
|
on po.ProviderId equals p.Id
|
||||||
|
where ou.UserId == _userId
|
||||||
|
select new ProviderOrganizationProviderDetails
|
||||||
|
{
|
||||||
|
Id = po.Id,
|
||||||
|
OrganizationId = po.OrganizationId,
|
||||||
|
ProviderId = po.ProviderId,
|
||||||
|
ProviderName = p.Name,
|
||||||
|
ProviderType = p.Type
|
||||||
|
};
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
{
|
{
|
||||||
var currentContext = new CurrentContext(null);
|
var currentContext = new CurrentContext(null, null);
|
||||||
await currentContext.BuildAsync(Context.User, _globalSettings);
|
await currentContext.BuildAsync(Context.User, _globalSettings);
|
||||||
if (currentContext.Organizations != null)
|
if (currentContext.Organizations != null)
|
||||||
{
|
{
|
||||||
@ -33,7 +33,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
|
|
||||||
public override async Task OnDisconnectedAsync(Exception exception)
|
public override async Task OnDisconnectedAsync(Exception exception)
|
||||||
{
|
{
|
||||||
var currentContext = new CurrentContext(null);
|
var currentContext = new CurrentContext(null, null);
|
||||||
await currentContext.BuildAsync(Context.User, _globalSettings);
|
await currentContext.BuildAsync(Context.User, _globalSettings);
|
||||||
if (currentContext.Organizations != null)
|
if (currentContext.Organizations != null)
|
||||||
{
|
{
|
||||||
|
@ -263,6 +263,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\ProviderOrganizationProviderDetails_ReadByUserId.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\Provider_ReadByOrganizationId.sql" />
|
<Build Include="dbo\Stored Procedures\Provider_ReadByOrganizationId.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\Organization_UnassignedToProviderSearch.sql" />
|
<Build Include="dbo\Stored Procedures\Organization_UnassignedToProviderSearch.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\ProviderOrganization_Create.sql" />
|
<Build Include="dbo\Stored Procedures\ProviderOrganization_Create.sql" />
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[ProviderOrganizationProviderDetails_ReadByUserId]
|
||||||
|
@UserId UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
PO.Id,
|
||||||
|
PO.OrganizationId,
|
||||||
|
PO.ProviderId,
|
||||||
|
P.Name as ProviderName,
|
||||||
|
P.[Type] as ProviderType
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderOrganizationView] PO
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUser] OU ON PO.OrganizationId = OU.OrganizationId
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[Provider] P ON PO.ProviderId = P.Id
|
||||||
|
WHERE
|
||||||
|
OU.UserId = @UserId
|
||||||
|
END
|
@ -35,6 +35,7 @@ SELECT
|
|||||||
OU.[Permissions],
|
OU.[Permissions],
|
||||||
PO.[ProviderId],
|
PO.[ProviderId],
|
||||||
P.[Name] ProviderName,
|
P.[Name] ProviderName,
|
||||||
|
P.[Type] ProviderType,
|
||||||
SS.[Data] SsoConfig,
|
SS.[Data] SsoConfig,
|
||||||
OS.[FriendlyName] FamilySponsorshipFriendlyName,
|
OS.[FriendlyName] FamilySponsorshipFriendlyName,
|
||||||
OS.[LastSyncDate] FamilySponsorshipLastSyncDate,
|
OS.[LastSyncDate] FamilySponsorshipLastSyncDate,
|
||||||
|
@ -24,6 +24,7 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IPaymentService _paymentService;
|
private readonly IPaymentService _paymentService;
|
||||||
private readonly IPolicyRepository _policyRepository;
|
private readonly IPolicyRepository _policyRepository;
|
||||||
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||||
private readonly ISsoConfigService _ssoConfigService;
|
private readonly ISsoConfigService _ssoConfigService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
@ -46,6 +47,7 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||||
_paymentService = Substitute.For<IPaymentService>();
|
_paymentService = Substitute.For<IPaymentService>();
|
||||||
_policyRepository = Substitute.For<IPolicyRepository>();
|
_policyRepository = Substitute.For<IPolicyRepository>();
|
||||||
|
_providerRepository = Substitute.For<IProviderRepository>();
|
||||||
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
|
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
|
||||||
_ssoConfigService = Substitute.For<ISsoConfigService>();
|
_ssoConfigService = Substitute.For<ISsoConfigService>();
|
||||||
_getOrganizationApiKeyQuery = Substitute.For<IGetOrganizationApiKeyQuery>();
|
_getOrganizationApiKeyQuery = Substitute.For<IGetOrganizationApiKeyQuery>();
|
||||||
@ -57,7 +59,7 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_updateOrganizationLicenseCommand = Substitute.For<IUpdateOrganizationLicenseCommand>();
|
_updateOrganizationLicenseCommand = Substitute.For<IUpdateOrganizationLicenseCommand>();
|
||||||
|
|
||||||
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
|
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
|
||||||
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
|
_policyRepository, _providerRepository, _organizationService, _userService, _paymentService, _currentContext,
|
||||||
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand,
|
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand,
|
||||||
_createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand,
|
_createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand,
|
||||||
_cloudGetOrganizationLicenseQuery, _globalSettings);
|
_cloudGetOrganizationLicenseQuery, _globalSettings);
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
OU.[UserId],
|
||||||
|
OU.[OrganizationId],
|
||||||
|
O.[Name],
|
||||||
|
O.[Enabled],
|
||||||
|
O.[PlanType],
|
||||||
|
O.[UsePolicies],
|
||||||
|
O.[UseSso],
|
||||||
|
O.[UseKeyConnector],
|
||||||
|
O.[UseScim],
|
||||||
|
O.[UseGroups],
|
||||||
|
O.[UseDirectory],
|
||||||
|
O.[UseEvents],
|
||||||
|
O.[UseTotp],
|
||||||
|
O.[Use2fa],
|
||||||
|
O.[UseApi],
|
||||||
|
O.[UseResetPassword],
|
||||||
|
O.[SelfHost],
|
||||||
|
O.[UsersGetPremium],
|
||||||
|
O.[UseCustomPermissions],
|
||||||
|
O.[UseSecretsManager],
|
||||||
|
O.[Seats],
|
||||||
|
O.[MaxCollections],
|
||||||
|
O.[MaxStorageGb],
|
||||||
|
O.[Identifier],
|
||||||
|
OU.[Key],
|
||||||
|
OU.[ResetPasswordKey],
|
||||||
|
O.[PublicKey],
|
||||||
|
O.[PrivateKey],
|
||||||
|
OU.[Status],
|
||||||
|
OU.[Type],
|
||||||
|
SU.[ExternalId] SsoExternalId,
|
||||||
|
OU.[Permissions],
|
||||||
|
PO.[ProviderId],
|
||||||
|
P.[Name] ProviderName,
|
||||||
|
P.[Type] ProviderType,
|
||||||
|
SS.[Data] SsoConfig,
|
||||||
|
OS.[FriendlyName] FamilySponsorshipFriendlyName,
|
||||||
|
OS.[LastSyncDate] FamilySponsorshipLastSyncDate,
|
||||||
|
OS.[ToDelete] FamilySponsorshipToDelete,
|
||||||
|
OS.[ValidUntil] FamilySponsorshipValidUntil,
|
||||||
|
OU.[AccessSecretsManager]
|
||||||
|
FROM
|
||||||
|
[dbo].[OrganizationUser] OU
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id]
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[Provider] P ON P.[Id] = PO.[ProviderId]
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId]
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE [dbo].[ProviderOrganizationProviderDetails_ReadByUserId]
|
||||||
|
@UserId UNIQUEIDENTIFIER
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
PO.Id,
|
||||||
|
PO.OrganizationId,
|
||||||
|
PO.ProviderId,
|
||||||
|
P.Name as ProviderName,
|
||||||
|
P.[Type] as ProviderType
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderOrganizationView] PO
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUser] OU ON PO.OrganizationId = OU.OrganizationId
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[Provider] P ON PO.ProviderId = P.Id
|
||||||
|
WHERE
|
||||||
|
OU.UserId = @UserId
|
||||||
|
END
|
||||||
|
GO
|
Reference in New Issue
Block a user