1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -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:
Rui Tomé
2023-04-03 10:57:07 +01:00
committed by GitHub
parent ed2c217ee6
commit 33d922310c
19 changed files with 266 additions and 39 deletions

View File

@ -28,6 +28,7 @@ public class OrganizationsController : Controller
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPolicyRepository _policyRepository;
private readonly IProviderRepository _providerRepository;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly IPaymentService _paymentService;
@ -46,6 +47,7 @@ public class OrganizationsController : Controller
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository,
IProviderRepository providerRepository,
IOrganizationService organizationService,
IUserService userService,
IPaymentService paymentService,
@ -63,6 +65,7 @@ public class OrganizationsController : Controller
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_policyRepository = policyRepository;
_providerRepository = providerRepository;
_organizationService = organizationService;
_userService = userService;
_paymentService = paymentService;
@ -101,7 +104,7 @@ public class OrganizationsController : Controller
public async Task<BillingResponseModel> GetBilling(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.ViewBillingHistory(orgIdGuid))
{
throw new NotFoundException();
}
@ -120,7 +123,7 @@ public class OrganizationsController : Controller
public async Task<OrganizationSubscriptionResponseModel> GetSubscription(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.ViewSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -139,7 +142,9 @@ public class OrganizationsController : Controller
throw new NotFoundException();
}
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo);
var hideSensitiveData = !await _currentContext.EditSubscription(orgIdGuid);
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData);
}
else
{
@ -240,7 +245,7 @@ public class OrganizationsController : Controller
model.BillingEmail != organization.BillingEmail);
var hasRequiredPermissions = updateBilling
? await _currentContext.ManageBilling(orgIdGuid)
? await _currentContext.EditSubscription(orgIdGuid)
: await _currentContext.OrganizationOwner(orgIdGuid);
if (!hasRequiredPermissions)
@ -257,7 +262,7 @@ public class OrganizationsController : Controller
public async Task PostPayment(string id, [FromBody] PaymentRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditPaymentMethods(orgIdGuid))
{
throw new NotFoundException();
}
@ -280,7 +285,7 @@ public class OrganizationsController : Controller
public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody] OrganizationUpgradeRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -294,7 +299,7 @@ public class OrganizationsController : Controller
public async Task PostSubscription(string id, [FromBody] OrganizationSubscriptionUpdateRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -307,7 +312,7 @@ public class OrganizationsController : Controller
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody] OrganizationSeatRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -321,7 +326,7 @@ public class OrganizationsController : Controller
public async Task<PaymentResponseModel> PostStorage(string id, [FromBody] StorageRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -335,7 +340,7 @@ public class OrganizationsController : Controller
public async Task PostVerifyBank(string id, [FromBody] OrganizationVerifyBankRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -348,7 +353,7 @@ public class OrganizationsController : Controller
public async Task PostCancel(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
@ -361,7 +366,7 @@ public class OrganizationsController : Controller
public async Task PostReinstate(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManageBilling(orgIdGuid))
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}

View File

@ -84,28 +84,30 @@ public class OrganizationResponseModel : ResponseModel
public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
{
public OrganizationSubscriptionResponseModel(Organization organization, SubscriptionInfo subscription = null)
: 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
public OrganizationSubscriptionResponseModel(Organization organization) : base(organization, "organizationSubscription")
{
Expiration = organization.ExpirationDate;
}
StorageName = organization.Storage.HasValue ?
CoreHelpers.ReadableBytesSize(organization.Storage.Value) : null;
StorageGb = organization.Storage.HasValue ?
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 double? StorageGb { get; set; }
public BillingSubscription Subscription { get; set; }

View File

@ -1,4 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.Enums.Provider;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
@ -46,6 +47,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
UserId = organization.UserId?.ToString();
ProviderId = organization.ProviderId?.ToString();
ProviderName = organization.ProviderName;
ProviderType = organization.ProviderType;
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
@ -97,6 +99,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
public bool HasPublicAndPrivateKeys { get; set; }
public string ProviderId { get; set; }
public string ProviderName { get; set; }
public ProviderType? ProviderType { get; set; }
public string FamilySponsorshipFriendlyName { get; set; }
public bool FamilySponsorshipAvailable { get; set; }
public ProductType PlanProductType { get; set; }

View File

@ -100,6 +100,6 @@ public class BillingSubscriptionUpcomingInvoice
Date = inv.Date;
}
public decimal Amount { get; set; }
public decimal? Amount { get; set; }
public DateTime? Date { get; set; }
}

View File

@ -13,9 +13,11 @@ namespace Bit.Core.Context;
public class CurrentContext : ICurrentContext
{
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderUserRepository _providerUserRepository;
private bool _builtHttpContext;
private bool _builtClaimsPrincipal;
private IEnumerable<ProviderOrganizationProviderDetails> _providerOrganizationProviderDetails;
private IEnumerable<ProviderUserOrganizationDetails> _providerUserOrganizations;
public virtual HttpContext HttpContext { get; set; }
@ -37,8 +39,11 @@ public class CurrentContext : ICurrentContext
public virtual ClientType ClientType { get; set; }
public virtual Guid? ServiceAccountOrganizationId { get; set; }
public CurrentContext(IProviderUserRepository providerUserRepository)
public CurrentContext(
IProviderOrganizationRepository providerOrganizationRepository,
IProviderUserRepository providerUserRepository)
{
_providerOrganizationRepository = providerOrganizationRepository;
_providerUserRepository = providerUserRepository;
}
@ -392,15 +397,34 @@ public class CurrentContext : ICurrentContext
&& (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
? await ProviderUserForOrgAsync(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)
{
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)
{
return (await GetProviderOrganizations()).Any(po => po.OrganizationId == orgId);
return (await GetProviderUserOrganizations()).Any(po => po.OrganizationId == orgId);
}
public async Task<Guid?> ProviderIdForOrg(Guid orgId)
@ -443,7 +467,7 @@ public class CurrentContext : ICurrentContext
return null;
}
var po = (await GetProviderOrganizations())
var po = (await GetProviderUserOrganizations())
?.FirstOrDefault(po => po.OrganizationId == orgId);
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)
{
@ -529,4 +553,14 @@ public class CurrentContext : ICurrentContext
return _providerUserOrganizations;
}
protected async Task<IEnumerable<ProviderOrganizationProviderDetails>> GetOrganizationProviderDetails()
{
if (_providerOrganizationProviderDetails == null && UserId.HasValue)
{
_providerOrganizationProviderDetails = await _providerOrganizationRepository.GetManyByUserAsync(UserId.Value);
}
return _providerOrganizationProviderDetails;
}
}

View File

@ -52,7 +52,10 @@ public interface ICurrentContext
Task<bool> ManageUsers(Guid orgId);
Task<bool> ManageScim(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);
bool ProviderProviderAdmin(Guid providerId);
bool ProviderUser(Guid providerId);

View File

@ -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
{
@ -36,6 +38,7 @@ public class OrganizationUserOrganizationDetails
public string PrivateKey { get; set; }
public Guid? ProviderId { get; set; }
public string ProviderName { get; set; }
public ProviderType? ProviderType { get; set; }
public string FamilySponsorshipFriendlyName { get; set; }
public string SsoConfig { get; set; }
public DateTime? FamilySponsorshipLastSyncDate { get; set; }

View File

@ -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; }
}

View File

@ -8,4 +8,5 @@ public interface IProviderOrganizationRepository : IRepository<ProviderOrganizat
Task<ICollection<ProviderOrganization>> CreateManyAsync(IEnumerable<ProviderOrganization> providerOrganizations);
Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId);
Task<ProviderOrganization> GetByOrganizationId(Guid organizationId);
Task<IEnumerable<ProviderOrganizationProviderDetails>> GetManyByUserAsync(Guid userId);
}

View File

@ -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)
{
var po = providerOrganizations.FirstOrDefault();

View File

@ -56,4 +56,15 @@ public class ProviderOrganizationRepository :
var dbContext = GetDatabaseContext(scope);
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;
}
}
}

View File

@ -55,6 +55,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
Permissions = ou.Permissions,
ProviderId = p.Id,
ProviderName = p.Name,
ProviderType = p.Type,
SsoConfig = ss.Data,
FamilySponsorshipFriendlyName = os.FriendlyName,
FamilySponsorshipLastSyncDate = os.LastSyncDate,

View File

@ -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;
}
}

View File

@ -18,7 +18,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
public override async Task OnConnectedAsync()
{
var currentContext = new CurrentContext(null);
var currentContext = new CurrentContext(null, null);
await currentContext.BuildAsync(Context.User, _globalSettings);
if (currentContext.Organizations != null)
{
@ -33,7 +33,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
public override async Task OnDisconnectedAsync(Exception exception)
{
var currentContext = new CurrentContext(null);
var currentContext = new CurrentContext(null, null);
await currentContext.BuildAsync(Context.User, _globalSettings);
if (currentContext.Organizations != null)
{

View File

@ -263,6 +263,7 @@
<Build Include="dbo\Stored Procedures\Policy_ReadByUserId.sql" />
<Build Include="dbo\Stored Procedures\Policy_Update.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\Organization_UnassignedToProviderSearch.sql" />
<Build Include="dbo\Stored Procedures\ProviderOrganization_Create.sql" />

View File

@ -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

View File

@ -35,6 +35,7 @@ SELECT
OU.[Permissions],
PO.[ProviderId],
P.[Name] ProviderName,
P.[Type] ProviderType,
SS.[Data] SsoConfig,
OS.[FriendlyName] FamilySponsorshipFriendlyName,
OS.[LastSyncDate] FamilySponsorshipLastSyncDate,

View File

@ -24,6 +24,7 @@ public class OrganizationsControllerTests : IDisposable
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly IProviderRepository _providerRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService;
private readonly IUserService _userService;
@ -46,6 +47,7 @@ public class OrganizationsControllerTests : IDisposable
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_policyRepository = Substitute.For<IPolicyRepository>();
_providerRepository = Substitute.For<IProviderRepository>();
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_ssoConfigService = Substitute.For<ISsoConfigService>();
_getOrganizationApiKeyQuery = Substitute.For<IGetOrganizationApiKeyQuery>();
@ -57,7 +59,7 @@ public class OrganizationsControllerTests : IDisposable
_updateOrganizationLicenseCommand = Substitute.For<IUpdateOrganizationLicenseCommand>();
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_policyRepository, _providerRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand,
_createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand,
_cloudGetOrganizationLicenseQuery, _globalSettings);

View File

@ -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