mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00
Finish emails
This commit is contained in:
@ -0,0 +1,23 @@
|
|||||||
|
{{#>FullHtmlLayout}}
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||||
|
You have been invited to join the <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{OrganizationName}}</b> organization. This link expires on <b>{{ExpirationDate}}.</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||||
|
<a href="{{{Url}}}" clicktracking=off target="_blank" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #175DDC; border-color: #175DDC; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
Join Organization Now
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||||
|
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||||
|
If you do not wish to join this organization, you can safely ignore this email.
|
||||||
|
<br />
|
||||||
|
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Did you know?</b> Members of {{OrganizationName}} receive a complimentary Families subscription. Learn more <a href="https://bitwarden.com/help/article/about-bitwarden-plans/#families-organizations" target="_blank" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #175DDC; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; text-decoration: underline;">here</a>.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{{/FullHtmlLayout}}
|
@ -0,0 +1,10 @@
|
|||||||
|
{{#>BasicTextLayout}}
|
||||||
|
You have been invited to join the {{OrganizationName}} organization. To accept this invite, click the following link:
|
||||||
|
|
||||||
|
{{{Url}}}
|
||||||
|
|
||||||
|
This link expires on {{ExpirationDate}}.
|
||||||
|
|
||||||
|
If you do not wish to join this organization, you can safely ignore this email.
|
||||||
|
Did you know? Members of {{OrganizationName}} receive a complimentary Families subscription. Learn more here: https://bitwarden.com/help/article/about-bitwarden-plans/#families-organizations
|
||||||
|
{{/BasicTextLayout}}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace Bit.Core.Models.Mail.FamiliesForEnterprise
|
||||||
|
{
|
||||||
|
public class FamiliesForEnterpriseOfferExistingAccountViewModel : BaseMailModel
|
||||||
|
{
|
||||||
|
public string SponsorEmail { get; set; }
|
||||||
|
public string SponsoredEmail { get; set; }
|
||||||
|
public string SponsorshipToken { get; set; }
|
||||||
|
public string Url => $"{WebVaultUrl}/?sponsorshipToken={SponsorshipToken}&email={SponsoredEmail}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace Bit.Core.Models.Mail.FamiliesForEnterprise
|
||||||
|
{
|
||||||
|
public class FamiliesForEnterpriseOfferNewAccountViewModel : BaseMailModel
|
||||||
|
{
|
||||||
|
public string SponsorEmail { get; set; }
|
||||||
|
public string SponsoredEmail { get; set; }
|
||||||
|
public string SponsorshipToken { get; set; }
|
||||||
|
public string Url => $"{WebVaultUrl}/register?sponsorshipToken={SponsorshipToken}&email={SponsoredEmail}";
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
namespace Bit.Core.Models.Mail.FamiliesForEnterprise
|
|
||||||
{
|
|
||||||
public class FamiliesForEnterpriseOfferViewModel : BaseMailModel
|
|
||||||
{
|
|
||||||
public string SponsorEmail { get; set; }
|
|
||||||
public string SponsorshipToken { get; set; }
|
|
||||||
public string Url => $"{WebVaultUrl}/sponsored/families-for-enterprise?token={SponsorshipToken}";
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,8 +18,8 @@ namespace Bit.Core.Services
|
|||||||
Task SendTwoFactorEmailAsync(string email, string token);
|
Task SendTwoFactorEmailAsync(string email, string token);
|
||||||
Task SendNoMasterPasswordHintEmailAsync(string email);
|
Task SendNoMasterPasswordHintEmailAsync(string email);
|
||||||
Task SendMasterPasswordHintEmailAsync(string email, string hint);
|
Task SendMasterPasswordHintEmailAsync(string email, string hint);
|
||||||
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token);
|
Task SendOrganizationInviteEmailAsync(string organizationName, bool orgCanSponsor, OrganizationUser orgUser, ExpiringToken token);
|
||||||
Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites);
|
Task BulkSendOrganizationInviteEmailAsync(string organizationName, bool orgCanSponsor, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites);
|
||||||
Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||||
Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> ownerEmails);
|
Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> ownerEmails);
|
||||||
Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable<string> adminEmails);
|
Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable<string> adminEmails);
|
||||||
|
@ -205,15 +205,15 @@ namespace Bit.Core.Services
|
|||||||
await _mailDeliveryService.SendEmailAsync(message);
|
await _mailDeliveryService.SendEmailAsync(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token) =>
|
public Task SendOrganizationInviteEmailAsync(string organizationName, bool orgCanSponsor, OrganizationUser orgUser, ExpiringToken token) =>
|
||||||
BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) });
|
BulkSendOrganizationInviteEmailAsync(organizationName, orgCanSponsor, new[] { (orgUser, token) });
|
||||||
|
|
||||||
public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites)
|
public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, bool orgCanSponsor, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites)
|
||||||
{
|
{
|
||||||
MailQueueMessage CreateMessage(string email, object model)
|
MailQueueMessage CreateMessage(string email, object model)
|
||||||
{
|
{
|
||||||
var message = CreateDefaultMessage($"Join {organizationName}", email);
|
var message = CreateDefaultMessage($"Join {organizationName}", email);
|
||||||
return new MailQueueMessage(message, "OrganizationUserInvited", model);
|
return new MailQueueMessage(message, orgCanSponsor ? "OrganizationUserInvitedWithSponsorship" : "OrganizationUserInvited", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageModels = invites.Select(invite => CreateMessage(invite.orgUser.Email,
|
var messageModels = invites.Select(invite => CreateMessage(invite.orgUser.Email,
|
||||||
@ -761,18 +761,33 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var message = CreateDefaultMessage("Finish Activation - Your Free Families Subscription", email);
|
var message = CreateDefaultMessage("Finish Activation - Your Free Families Subscription", email);
|
||||||
|
|
||||||
var model = new FamiliesForEnterpriseOfferViewModel
|
if (existingAccount)
|
||||||
{
|
{
|
||||||
SponsorEmail = sponsorEmail,
|
var model = new FamiliesForEnterpriseOfferExistingAccountViewModel
|
||||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
{
|
||||||
SiteName = _globalSettings.SiteName,
|
SponsorEmail = sponsorEmail,
|
||||||
SponsorshipToken = token,
|
SponsoredEmail = WebUtility.UrlEncode(email),
|
||||||
};
|
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||||
|
SiteName = _globalSettings.SiteName,
|
||||||
|
SponsorshipToken = token,
|
||||||
|
};
|
||||||
|
|
||||||
|
await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseOfferExistingAccount", model);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var model = new FamiliesForEnterpriseOfferNewAccountViewModel
|
||||||
|
{
|
||||||
|
SponsorEmail = sponsorEmail,
|
||||||
|
SponsoredEmail = WebUtility.UrlEncode(email),
|
||||||
|
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||||
|
SiteName = _globalSettings.SiteName,
|
||||||
|
SponsorshipToken = token,
|
||||||
|
};
|
||||||
|
|
||||||
|
await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseOfferNewAccount", model);
|
||||||
|
}
|
||||||
|
|
||||||
await AddMessageContentAsync(message, existingAccount
|
|
||||||
? "FamiliesForEnterprise.FamiliesForEnterpriseOfferExistingAccount"
|
|
||||||
: "FamiliesForEnterprise.FamiliesForEnterpriseOfferNewAccount", model);
|
|
||||||
|
|
||||||
message.Category = "FamiliesForEnterpriseOffer";
|
message.Category = "FamiliesForEnterpriseOffer";
|
||||||
await _mailDeliveryService.SendEmailAsync(message);
|
await _mailDeliveryService.SendEmailAsync(message);
|
||||||
}
|
}
|
||||||
|
@ -1239,7 +1239,10 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
string MakeToken(OrganizationUser orgUser) =>
|
string MakeToken(OrganizationUser orgUser) =>
|
||||||
_dataProtector.Protect($"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
_dataProtector.Protect($"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
|
||||||
await _mailService.BulkSendOrganizationInviteEmailAsync(organization.Name,
|
|
||||||
|
var orgCanSponsor = CheckOrgCanSponsor(organization);
|
||||||
|
|
||||||
|
await _mailService.BulkSendOrganizationInviteEmailAsync(organization.Name, orgCanSponsor,
|
||||||
orgUsers.Select(o => (o, new ExpiringToken(MakeToken(o), DateTime.UtcNow.AddDays(5)))));
|
orgUsers.Select(o => (o, new ExpiringToken(MakeToken(o), DateTime.UtcNow.AddDays(5)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1249,7 +1252,19 @@ namespace Bit.Core.Services
|
|||||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(now);
|
var nowMillis = CoreHelpers.ToEpocMilliseconds(now);
|
||||||
var token = _dataProtector.Protect(
|
var token = _dataProtector.Protect(
|
||||||
$"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}");
|
$"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}");
|
||||||
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)));
|
|
||||||
|
// TODO: Refactor so that the below line can be used.
|
||||||
|
// StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise).UsersCanSponsor(organization)
|
||||||
|
var orgCanSponsor = CheckOrgCanSponsor(organization);
|
||||||
|
|
||||||
|
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgCanSponsor, orgUser, new ExpiringToken(token, now.AddDays(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool CheckOrgCanSponsor(Organization organization)
|
||||||
|
{
|
||||||
|
return StaticStore.GetPlan(organization.PlanType).Product == ProductType.Enterprise
|
||||||
|
&& !organization.SelfHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
||||||
|
@ -55,12 +55,12 @@ namespace Bit.Core.Services
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token)
|
public Task SendOrganizationInviteEmailAsync(string organizationName, bool orgCanSponsor, OrganizationUser orgUser, ExpiringToken token)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites)
|
public Task BulkSendOrganizationInviteEmailAsync(string organizationName, bool orgCanSponsor, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ namespace Bit.Core.Test.Services
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "Only for local development")]
|
[Fact]
|
||||||
public async Task SendAllEmails()
|
public async Task SendAllEmails()
|
||||||
{
|
{
|
||||||
// This test is only opt in and is more for development purposes.
|
// This test is only opt in and is more for development purposes.
|
||||||
@ -115,6 +115,9 @@ namespace Bit.Core.Test.Services
|
|||||||
{ ("familyUserEmail", typeof(string)), "test@bitwarden.com" },
|
{ ("familyUserEmail", typeof(string)), "test@bitwarden.com" },
|
||||||
{ ("sponsorEmail", typeof(string)), "test@bitwarden.com" },
|
{ ("sponsorEmail", typeof(string)), "test@bitwarden.com" },
|
||||||
{ ("familyOrgName", typeof(string)), "Test Org Name" },
|
{ ("familyOrgName", typeof(string)), "Test Org Name" },
|
||||||
|
{ ("orgCanSponsor", typeof(bool)), true },
|
||||||
|
{ ("existingAccount", typeof(bool)), true },
|
||||||
|
{ ("sponsorshipEndDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1)},
|
||||||
};
|
};
|
||||||
|
|
||||||
var globalSettings = new GlobalSettings
|
var globalSettings = new GlobalSettings
|
||||||
|
@ -28,7 +28,7 @@ namespace Bit.Core.Test.Services
|
|||||||
public class OrganizationServiceTests
|
public class OrganizationServiceTests
|
||||||
{
|
{
|
||||||
// [Fact]
|
// [Fact]
|
||||||
[Theory, PaidOrganizationAutoData]
|
[Theory, PaidOrganizationAutoData(PlanType.EnterpriseAnnually)]
|
||||||
public async Task OrgImportCreateNewUsers(SutProvider<OrganizationService> sutProvider, Guid userId,
|
public async Task OrgImportCreateNewUsers(SutProvider<OrganizationService> sutProvider, Guid userId,
|
||||||
Organization org, List<OrganizationUserUserDetails> existingUsers, List<ImportedOrganizationUser> newUsers)
|
Organization org, List<OrganizationUserUserDetails> existingUsers, List<ImportedOrganizationUser> newUsers)
|
||||||
{
|
{
|
||||||
@ -66,6 +66,7 @@ namespace Bit.Core.Test.Services
|
|||||||
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
|
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
|
||||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||||
.BulkSendOrganizationInviteEmailAsync(org.Name,
|
.BulkSendOrganizationInviteEmailAsync(org.Name,
|
||||||
|
true,
|
||||||
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount));
|
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount));
|
||||||
|
|
||||||
// Send events
|
// Send events
|
||||||
@ -124,6 +125,7 @@ namespace Bit.Core.Test.Services
|
|||||||
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
|
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
|
||||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||||
.BulkSendOrganizationInviteEmailAsync(org.Name,
|
.BulkSendOrganizationInviteEmailAsync(org.Name,
|
||||||
|
false,
|
||||||
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount));
|
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount));
|
||||||
|
|
||||||
// Sent events
|
// Sent events
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView')
|
||||||
|
BEGIN
|
||||||
|
DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
OU.[UserId],
|
||||||
|
OU.[OrganizationId],
|
||||||
|
O.[Name],
|
||||||
|
O.[Enabled],
|
||||||
|
O.[PlanType],
|
||||||
|
O.[UsePolicies],
|
||||||
|
O.[UseSso],
|
||||||
|
O.[UseGroups],
|
||||||
|
O.[UseDirectory],
|
||||||
|
O.[UseEvents],
|
||||||
|
O.[UseTotp],
|
||||||
|
O.[Use2fa],
|
||||||
|
O.[UseApi],
|
||||||
|
O.[UseResetPassword],
|
||||||
|
O.[SelfHost],
|
||||||
|
O.[UsersGetPremium],
|
||||||
|
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,
|
||||||
|
SS.[Data] SsoConfig,
|
||||||
|
OS.[FriendlyName] FamilySponsorshipFriendlyName
|
||||||
|
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]
|
Reference in New Issue
Block a user