1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-01 23:31:41 +01:00

Organization User Accepted Invite Email Notifications (#1465)

This commit is contained in:
Addison Beck 2021-07-16 13:49:27 -04:00 committed by GitHub
parent 7abb053914
commit 5ec37b96b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 99 additions and 16 deletions

View File

@ -2,17 +2,23 @@
<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;" valign="top">
This email is to notify you that {{UserEmail}} has accepted your invitation to join <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>.
{{UserIdentifier}} needs to be confirmed to {{OrganizationName}} before they can access the organization vault.
</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;" valign="top">
To confirm this user, log into the Bitwarden web vault, manage your organization "People", and confirm the user.
To confirm users into your organization:
<ol>
<li>Log in to your <a href="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/manage/people">Web Vault</a> and open your Organization.</li>
<li>Open the <b>Manage</b> tab and select <b>People</b> from the left-hand menu.</li>
<li>Hover over the <b>Accepted</b> user and select the gear dropdown.</li>
<li>Select <b>Confirm</b>.</li>
</ol>
</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;" valign="top">
If you do not wish to confirm this user, you can also remove them from the organization on the same page.
For more information, please refer to the following help article: <a href="https://bitwarden.com/help/article/managing-users/#confirm">https://bitwarden.com/help/article/managing-users/#confirm</a>
</td>
</tr>
</table>

View File

@ -1,7 +1,11 @@
{{#>BasicTextLayout}}
This email is to notify you that {{UserEmail}} has accepted your invitation to join {{OrganizationName}}.
{{UserIdentifier}} needs to be confirmed to {{OrganizationName}} before they can access the organization vault.
To confirm this user, log into the Bitwarden web vault, manage your organization "People" and confirm the user.
To confirm users into your organization:
1. Log in to your Web Vault and open your Organization.
2. Open the Manage tab and select People from the left-hand menu.
3. Hover over the Accepted user and select the grear dropdown.
4. Select Confirm.
If you do not wish to confirm this user, you can also remove them from the organization on the same page.
{{/BasicTextLayout}}
For more information, please refer to the following help article: https://bitwarden.com/help/article/managing-user/#confirm
{{/BasicTextLayout}}

View File

@ -1,8 +1,11 @@
namespace Bit.Core.Models.Mail
using System;
namespace Bit.Core.Models.Mail
{
public class OrganizationUserAcceptedViewModel : BaseMailModel
{
public Guid OrganizationId { get; set; }
public string OrganizationName { get; set; }
public string UserEmail { get; set; }
public string UserIdentifier { get; set; }
}
}

View File

@ -389,5 +389,21 @@ namespace Bit.Core.Repositories.EntityFramework
}
Task<ICollection<string>> IOrganizationUserRepository.SelectKnownEmailsAsync(Guid organizationId, IEnumerable<string> emails, bool onlyRegisteredUsers) => throw new NotImplementedException();
public async Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = dbContext.OrganizationUsers
.Include(e => e.User)
.Where(e => e.OrganizationId.Equals(organizationId) && e.Type <= minRole)
.Select(e => new OrganizationUserUserDetails() {
Id = e.Id,
Email = e.Email ?? e.User.Email
});
return await query.ToListAsync();
}
}
}
}

View File

@ -37,5 +37,6 @@ namespace Bit.Core.Repositories
Task DeleteManyAsync(IEnumerable<Guid> userIds);
Task<OrganizationUser> GetByOrganizationEmailAsync(Guid organizationId, string email);
Task<IEnumerable<OrganizationUserPublicKey>> GetManyPublicKeysByOrganizationUserAsync(Guid organizationId, IEnumerable<Guid> Ids);
Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole);
}
}

View File

@ -391,5 +391,18 @@ namespace Bit.Core.Repositories.SqlServer
return results.ToList();
}
}
public async Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<OrganizationUserUserDetails>(
"[dbo].[OrganizationUser_ReadByMinimumRole]",
new { OrganizationId = organizationId, MinRole = minRole },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
}
}

View File

@ -19,7 +19,7 @@ namespace Bit.Core.Services
Task SendMasterPasswordHintEmailAsync(string email, string hint);
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token);
Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, string token)> invites);
Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail,
Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier,
IEnumerable<string> adminEmails);
Task SendOrganizationConfirmedEmailAsync(string organizationName, string email);
Task SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(string organizationName, string email);

View File

@ -143,14 +143,15 @@ namespace Bit.Core.Services
await _mailDeliveryService.SendEmailAsync(message);
}
public async Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail,
public async Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier,
IEnumerable<string> adminEmails)
{
var message = CreateDefaultMessage($"User {userEmail} Has Accepted Invite", adminEmails);
var message = CreateDefaultMessage($"Action Required: {userIdentifier} Needs to Be Confirmed", adminEmails);
var model = new OrganizationUserAcceptedViewModel
{
OrganizationName = CoreHelpers.SanitizeForEmail(organizationName),
UserEmail = userEmail,
OrganizationId = organization.Id,
OrganizationName = CoreHelpers.SanitizeForEmail(organization.Name),
UserIdentifier = userIdentifier,
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
SiteName = _globalSettings.SiteName
};

View File

@ -1424,7 +1424,10 @@ namespace Bit.Core.Services
await _organizationUserRepository.ReplaceAsync(orgUser);
// TODO: send notification emails to org admins and accepting user?
await _mailService.SendOrganizationAcceptedEmailAsync(
(await _organizationRepository.GetByIdAsync(orgUser.OrganizationId)),
user.Email,
(await _organizationUserRepository.GetManyByMinimumRoleAsync(orgUser.OrganizationId, OrganizationUserType.Admin)).Select(a => a.Email).Distinct());
return orgUser;
}

View File

@ -34,7 +34,7 @@ namespace Bit.Core.Services
return Task.FromResult(0);
}
public Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, IEnumerable<string> adminEmails)
public Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable<string> adminEmails)
{
return Task.FromResult(0);
}

View File

@ -0,0 +1,15 @@
CREATE PROCEDURE [dbo].[OrganizationUser_ReadByMinimumRole]
@OrganizationId UNIQUEIDENTIFIER,
@MinRole TINYINT
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[OrganizationUserUserDetailsView]
WHERE
OrganizationId = @OrganizationId
AND [Type] <= @MinRole
END

View File

@ -0,0 +1,21 @@
IF OBJECT_ID('[dbo].[OrganizationUser_ReadByMinimumRole]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[OrganizationUser_ReadByMinimumRole]
END
GO
CREATE PROCEDURE [dbo].[OrganizationUser_ReadByMinimumRole]
@OrganizationId UNIQUEIDENTIFIER,
@MinRole TINYINT
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[OrganizationUserUserDetailsView]
WHERE
OrganizationId = @OrganizationId
AND [Type] <= @MinRole
END