mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 05:00:19 -05:00
Merge branch 'main' into PM-18170-Remove-PM-15814-alert
This commit is contained in:
commit
904a5be2ab
@ -118,6 +118,7 @@ public static class FeatureFlagKeys
|
|||||||
public const string ExportAttachments = "export-attachments";
|
public const string ExportAttachments = "export-attachments";
|
||||||
|
|
||||||
/* Vault Team */
|
/* Vault Team */
|
||||||
|
public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge";
|
||||||
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
|
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
|
||||||
public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss";
|
public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss";
|
||||||
public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss";
|
public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss";
|
||||||
@ -125,6 +126,9 @@ public static class FeatureFlagKeys
|
|||||||
public const string RestrictProviderAccess = "restrict-provider-access";
|
public const string RestrictProviderAccess = "restrict-provider-access";
|
||||||
public const string SecurityTasks = "security-tasks";
|
public const string SecurityTasks = "security-tasks";
|
||||||
|
|
||||||
|
/* Auth Team */
|
||||||
|
public const string PM9112DeviceApprovalPersistence = "pm-9112-device-approval-persistence";
|
||||||
|
|
||||||
public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair";
|
public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair";
|
||||||
public const string UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection";
|
public const string UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection";
|
||||||
public const string DuoRedirect = "duo-redirect";
|
public const string DuoRedirect = "duo-redirect";
|
||||||
@ -176,6 +180,8 @@ public static class FeatureFlagKeys
|
|||||||
public const string PM12276Breadcrumbing = "pm-12276-breadcrumbing-for-business-features";
|
public const string PM12276Breadcrumbing = "pm-12276-breadcrumbing-for-business-features";
|
||||||
public const string PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method";
|
public const string PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method";
|
||||||
public const string PM3553_MobileSimpleLoginSelfHostAlias = "simple-login-self-host-alias";
|
public const string PM3553_MobileSimpleLoginSelfHostAlias = "simple-login-self-host-alias";
|
||||||
|
public const string SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor";
|
||||||
|
public const string ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor";
|
||||||
|
|
||||||
public static List<string> GetAllKeys()
|
public static List<string> GetAllKeys()
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</tr>
|
</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;">
|
<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" align="left">
|
<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" align="left">
|
||||||
To leave an organization, first log into the <a href="https://vault.bitwarden.com/#/login">web app</a>, select the three dot menu next to the organization name, and select Leave.
|
To leave an organization, first log into the <a href="{{{WebVaultUrl}}}/login">web app</a>, select the three dot menu next to the organization name, and select Leave.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{{#>BasicTextLayout}}
|
{{#>BasicTextLayout}}
|
||||||
Your user account has been revoked from the {{OrganizationName}} organization because your account is part of multiple organizations. Before you can rejoin {{OrganizationName}}, you must first leave all other organizations.
|
Your user account has been revoked from the {{OrganizationName}} organization because your account is part of multiple organizations. Before you can rejoin {{OrganizationName}}, you must first leave all other organizations.
|
||||||
|
|
||||||
To leave an organization, first log in the web app (https://vault.bitwarden.com/#/login), select the three dot menu next to the organization name, and select Leave.
|
To leave an organization, first log in the web app ({{{WebVaultUrl}}}/login), select the three dot menu next to the organization name, and select Leave.
|
||||||
{{/BasicTextLayout}}
|
{{/BasicTextLayout}}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; box-sizing: border-box; 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">
|
<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="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/billing/subscription" 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;">
|
<a href="{{{VaultSubscriptionUrl}}}" 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;">
|
||||||
Manage subscription
|
Manage subscription
|
||||||
</a>
|
</a>
|
||||||
<br class="line-break" />
|
<br class="line-break" />
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; box-sizing: border-box; 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">
|
<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="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/billing/subscription" 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;">
|
<a href="{{{VaultSubscriptionUrl}}}" 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;">
|
||||||
Manage subscription
|
Manage subscription
|
||||||
</a>
|
</a>
|
||||||
<br class="line-break" />
|
<br class="line-break" />
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; box-sizing: border-box; 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">
|
<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="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/billing/subscription" clicktracking=off target="_blank" rel="noopener noreferrer" 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;">
|
<a href="{{{VaultSubscriptionUrl}}}" clicktracking=off target="_blank" rel="noopener noreferrer" 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;">
|
||||||
Manage subscription
|
Manage subscription
|
||||||
</a>
|
</a>
|
||||||
<br class="line-break" />
|
<br class="line-break" />
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
<tr style="margin: 0; box-sizing: border-box; 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">
|
<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="https://vault.bitwarden.com/#/organizations/{{{OrganizationId}}}/billing/subscription" clicktracking=off target="_blank" rel="noopener noreferrer" 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;">
|
<a href="{{{VaultSubscriptionUrl}}}" clicktracking=off target="_blank" rel="noopener noreferrer" 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;">
|
||||||
Manage subscription
|
Manage subscription
|
||||||
</a>
|
</a>
|
||||||
<br class="line-break" />
|
<br class="line-break" />
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
public class OrganizationSeatsAutoscaledViewModel : BaseMailModel
|
public class OrganizationSeatsAutoscaledViewModel : BaseMailModel
|
||||||
{
|
{
|
||||||
public Guid OrganizationId { get; set; }
|
|
||||||
public int InitialSeatCount { get; set; }
|
public int InitialSeatCount { get; set; }
|
||||||
public int CurrentSeatCount { get; set; }
|
public int CurrentSeatCount { get; set; }
|
||||||
|
public string VaultSubscriptionUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
public class OrganizationSeatsMaxReachedViewModel : BaseMailModel
|
public class OrganizationSeatsMaxReachedViewModel : BaseMailModel
|
||||||
{
|
{
|
||||||
public Guid OrganizationId { get; set; }
|
|
||||||
public int MaxSeatCount { get; set; }
|
public int MaxSeatCount { get; set; }
|
||||||
|
public string VaultSubscriptionUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
public class OrganizationServiceAccountsMaxReachedViewModel
|
public class OrganizationServiceAccountsMaxReachedViewModel
|
||||||
{
|
{
|
||||||
public Guid OrganizationId { get; set; }
|
|
||||||
public int MaxServiceAccountsCount { get; set; }
|
public int MaxServiceAccountsCount { get; set; }
|
||||||
|
public string VaultSubscriptionUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -99,5 +99,5 @@ public interface IMailService
|
|||||||
string organizationName);
|
string organizationName);
|
||||||
Task SendClaimedDomainUserEmailAsync(ManagedUserDomainClaimedEmails emailList);
|
Task SendClaimedDomainUserEmailAsync(ManagedUserDomainClaimedEmails emailList);
|
||||||
Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<string> adminEmails, Guid organizationId, string email, string userName);
|
Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<string> adminEmails, Guid organizationId, string email, string userName);
|
||||||
Task SendBulkSecurityTaskNotificationsAsync(string orgName, IEnumerable<UserSecurityTasksCount> securityTaskNotificaitons);
|
Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications);
|
||||||
}
|
}
|
||||||
|
@ -214,9 +214,9 @@ public class HandlebarsMailService : IMailService
|
|||||||
var message = CreateDefaultMessage($"{organization.DisplayName()} Seat Count Has Increased", ownerEmails);
|
var message = CreateDefaultMessage($"{organization.DisplayName()} Seat Count Has Increased", ownerEmails);
|
||||||
var model = new OrganizationSeatsAutoscaledViewModel
|
var model = new OrganizationSeatsAutoscaledViewModel
|
||||||
{
|
{
|
||||||
OrganizationId = organization.Id,
|
|
||||||
InitialSeatCount = initialSeatCount,
|
InitialSeatCount = initialSeatCount,
|
||||||
CurrentSeatCount = organization.Seats.Value,
|
CurrentSeatCount = organization.Seats.Value,
|
||||||
|
VaultSubscriptionUrl = GetCloudVaultSubscriptionUrl(organization.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
await AddMessageContentAsync(message, "OrganizationSeatsAutoscaled", model);
|
await AddMessageContentAsync(message, "OrganizationSeatsAutoscaled", model);
|
||||||
@ -229,8 +229,8 @@ public class HandlebarsMailService : IMailService
|
|||||||
var message = CreateDefaultMessage($"{organization.DisplayName()} Seat Limit Reached", ownerEmails);
|
var message = CreateDefaultMessage($"{organization.DisplayName()} Seat Limit Reached", ownerEmails);
|
||||||
var model = new OrganizationSeatsMaxReachedViewModel
|
var model = new OrganizationSeatsMaxReachedViewModel
|
||||||
{
|
{
|
||||||
OrganizationId = organization.Id,
|
|
||||||
MaxSeatCount = maxSeatCount,
|
MaxSeatCount = maxSeatCount,
|
||||||
|
VaultSubscriptionUrl = GetCloudVaultSubscriptionUrl(organization.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
await AddMessageContentAsync(message, "OrganizationSeatsMaxReached", model);
|
await AddMessageContentAsync(message, "OrganizationSeatsMaxReached", model);
|
||||||
@ -1103,8 +1103,8 @@ public class HandlebarsMailService : IMailService
|
|||||||
var message = CreateDefaultMessage($"{organization.DisplayName()} Secrets Manager Seat Limit Reached", ownerEmails);
|
var message = CreateDefaultMessage($"{organization.DisplayName()} Secrets Manager Seat Limit Reached", ownerEmails);
|
||||||
var model = new OrganizationSeatsMaxReachedViewModel
|
var model = new OrganizationSeatsMaxReachedViewModel
|
||||||
{
|
{
|
||||||
OrganizationId = organization.Id,
|
|
||||||
MaxSeatCount = maxSeatCount,
|
MaxSeatCount = maxSeatCount,
|
||||||
|
VaultSubscriptionUrl = GetCloudVaultSubscriptionUrl(organization.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
await AddMessageContentAsync(message, "OrganizationSmSeatsMaxReached", model);
|
await AddMessageContentAsync(message, "OrganizationSmSeatsMaxReached", model);
|
||||||
@ -1118,8 +1118,8 @@ public class HandlebarsMailService : IMailService
|
|||||||
var message = CreateDefaultMessage($"{organization.DisplayName()} Secrets Manager Machine Accounts Limit Reached", ownerEmails);
|
var message = CreateDefaultMessage($"{organization.DisplayName()} Secrets Manager Machine Accounts Limit Reached", ownerEmails);
|
||||||
var model = new OrganizationServiceAccountsMaxReachedViewModel
|
var model = new OrganizationServiceAccountsMaxReachedViewModel
|
||||||
{
|
{
|
||||||
OrganizationId = organization.Id,
|
|
||||||
MaxServiceAccountsCount = maxSeatCount,
|
MaxServiceAccountsCount = maxSeatCount,
|
||||||
|
VaultSubscriptionUrl = GetCloudVaultSubscriptionUrl(organization.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
await AddMessageContentAsync(message, "OrganizationSmServiceAccountsMaxReached", model);
|
await AddMessageContentAsync(message, "OrganizationSmServiceAccountsMaxReached", model);
|
||||||
@ -1201,21 +1201,22 @@ public class HandlebarsMailService : IMailService
|
|||||||
await _mailDeliveryService.SendEmailAsync(message);
|
await _mailDeliveryService.SendEmailAsync(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendBulkSecurityTaskNotificationsAsync(string orgName, IEnumerable<UserSecurityTasksCount> securityTaskNotificaitons)
|
public async Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications)
|
||||||
{
|
{
|
||||||
MailQueueMessage CreateMessage(UserSecurityTasksCount notification)
|
MailQueueMessage CreateMessage(UserSecurityTasksCount notification)
|
||||||
{
|
{
|
||||||
var message = CreateDefaultMessage($"{orgName} has identified {notification.TaskCount} at-risk password{(notification.TaskCount.Equals(1) ? "" : "s")}", notification.Email);
|
var sanitizedOrgName = CoreHelpers.SanitizeForEmail(org.DisplayName(), false);
|
||||||
|
var message = CreateDefaultMessage($"{sanitizedOrgName} has identified {notification.TaskCount} at-risk password{(notification.TaskCount.Equals(1) ? "" : "s")}", notification.Email);
|
||||||
var model = new SecurityTaskNotificationViewModel
|
var model = new SecurityTaskNotificationViewModel
|
||||||
{
|
{
|
||||||
OrgName = orgName,
|
OrgName = CoreHelpers.SanitizeForEmail(sanitizedOrgName, false),
|
||||||
TaskCount = notification.TaskCount,
|
TaskCount = notification.TaskCount,
|
||||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||||
};
|
};
|
||||||
message.Category = "SecurityTasksNotification";
|
message.Category = "SecurityTasksNotification";
|
||||||
return new MailQueueMessage(message, "SecurityTasksNotification", model);
|
return new MailQueueMessage(message, "SecurityTasksNotification", model);
|
||||||
}
|
}
|
||||||
var messageModels = securityTaskNotificaitons.Select(CreateMessage);
|
var messageModels = securityTaskNotifications.Select(CreateMessage);
|
||||||
await EnqueueMailAsync(messageModels.ToList());
|
await EnqueueMailAsync(messageModels.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1223,4 +1224,11 @@ public class HandlebarsMailService : IMailService
|
|||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(userName) ? email : CoreHelpers.SanitizeForEmail(userName, false);
|
return string.IsNullOrEmpty(userName) ? email : CoreHelpers.SanitizeForEmail(userName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCloudVaultSubscriptionUrl(Guid organizationId)
|
||||||
|
=> _globalSettings.BaseServiceUri.CloudRegion?.ToLower() switch
|
||||||
|
{
|
||||||
|
"eu" => $"https://vault.bitwarden.eu/#/organizations/{organizationId}/billing/subscription",
|
||||||
|
_ => $"https://vault.bitwarden.com/#/organizations/{organizationId}/billing/subscription"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -324,7 +324,7 @@ public class NoopMailService : IMailService
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendBulkSecurityTaskNotificationsAsync(string orgName, IEnumerable<UserSecurityTasksCount> securityTaskNotificaitons)
|
public Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ public class CreateManyTaskNotificationsCommand : ICreateManyTaskNotificationsCo
|
|||||||
|
|
||||||
var organization = await _organizationRepository.GetByIdAsync(orgId);
|
var organization = await _organizationRepository.GetByIdAsync(orgId);
|
||||||
|
|
||||||
await _mailService.SendBulkSecurityTaskNotificationsAsync(organization.Name, userTaskCount);
|
await _mailService.SendBulkSecurityTaskNotificationsAsync(organization, userTaskCount);
|
||||||
|
|
||||||
// Break securityTaskCiphers into separate lists by user Id
|
// Break securityTaskCiphers into separate lists by user Id
|
||||||
var securityTaskCiphersByUser = securityTaskCiphers.GroupBy(x => x.UserId)
|
var securityTaskCiphersByUser = securityTaskCiphers.GroupBy(x => x.UserId)
|
||||||
|
@ -13,7 +13,9 @@ using Bit.Core.Tools.Models.Business;
|
|||||||
using Bit.Core.Tools.Services;
|
using Bit.Core.Tools.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
using Bit.Core.Vault.Enums;
|
||||||
using Bit.Core.Vault.Models.Data;
|
using Bit.Core.Vault.Models.Data;
|
||||||
|
using Bit.Core.Vault.Queries;
|
||||||
using Bit.Core.Vault.Repositories;
|
using Bit.Core.Vault.Repositories;
|
||||||
|
|
||||||
namespace Bit.Core.Vault.Services;
|
namespace Bit.Core.Vault.Services;
|
||||||
@ -38,6 +40,7 @@ public class CipherService : ICipherService
|
|||||||
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
|
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
private readonly IReferenceEventService _referenceEventService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IGetCipherPermissionsForUserQuery _getCipherPermissionsForUserQuery;
|
||||||
|
|
||||||
public CipherService(
|
public CipherService(
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
@ -54,7 +57,8 @@ public class CipherService : ICipherService
|
|||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IReferenceEventService referenceEventService,
|
IReferenceEventService referenceEventService,
|
||||||
ICurrentContext currentContext)
|
ICurrentContext currentContext,
|
||||||
|
IGetCipherPermissionsForUserQuery getCipherPermissionsForUserQuery)
|
||||||
{
|
{
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_folderRepository = folderRepository;
|
_folderRepository = folderRepository;
|
||||||
@ -71,6 +75,7 @@ public class CipherService : ICipherService
|
|||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_referenceEventService = referenceEventService;
|
_referenceEventService = referenceEventService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
|
_getCipherPermissionsForUserQuery = getCipherPermissionsForUserQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
|
public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
|
||||||
@ -161,6 +166,7 @@ public class CipherService : ICipherService
|
|||||||
{
|
{
|
||||||
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
|
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
|
||||||
cipher.RevisionDate = DateTime.UtcNow;
|
cipher.RevisionDate = DateTime.UtcNow;
|
||||||
|
await ValidateViewPasswordUserAsync(cipher);
|
||||||
await _cipherRepository.ReplaceAsync(cipher);
|
await _cipherRepository.ReplaceAsync(cipher);
|
||||||
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Updated);
|
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Updated);
|
||||||
|
|
||||||
@ -966,4 +972,32 @@ public class CipherService : ICipherService
|
|||||||
|
|
||||||
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
|
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ValidateViewPasswordUserAsync(Cipher cipher)
|
||||||
|
{
|
||||||
|
if (cipher.Type != CipherType.Login || cipher.Data == null || !cipher.OrganizationId.HasValue)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var existingCipher = await _cipherRepository.GetByIdAsync(cipher.Id);
|
||||||
|
if (existingCipher == null) return;
|
||||||
|
|
||||||
|
var cipherPermissions = await _getCipherPermissionsForUserQuery.GetByOrganization(cipher.OrganizationId.Value);
|
||||||
|
// Check if user is a "hidden password" user
|
||||||
|
if (!cipherPermissions.TryGetValue(cipher.Id, out var permission) || !(permission.ViewPassword && permission.Edit))
|
||||||
|
{
|
||||||
|
// "hidden password" users may not add cipher key encryption
|
||||||
|
if (existingCipher.Key == null && cipher.Key != null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You do not have permission to add cipher key encryption.");
|
||||||
|
}
|
||||||
|
// "hidden password" users may not change passwords, TOTP codes, or passkeys, so we need to set them back to the original values
|
||||||
|
var existingCipherData = JsonSerializer.Deserialize<CipherLoginData>(existingCipher.Data);
|
||||||
|
var newCipherData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data);
|
||||||
|
newCipherData.Fido2Credentials = existingCipherData.Fido2Credentials;
|
||||||
|
newCipherData.Totp = existingCipherData.Totp;
|
||||||
|
newCipherData.Password = existingCipherData.Password;
|
||||||
|
cipher.Data = JsonSerializer.Serialize(newCipherData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Icons' " />
|
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Icons' " />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AngleSharp" Version="1.1.2" />
|
<PackageReference Include="AngleSharp" Version="1.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -563,8 +563,8 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
|
|||||||
await using var connection = new SqlConnection(ConnectionString);
|
await using var connection = new SqlConnection(ConnectionString);
|
||||||
|
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
"[dbo].[OrganizationUser_SetStatusForUsersById]",
|
"[dbo].[OrganizationUser_SetStatusForUsersByGuidIdArray]",
|
||||||
new { OrganizationUserIds = JsonSerializer.Serialize(organizationUserIds), Status = OrganizationUserStatusType.Revoked },
|
new { OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP(), Status = OrganizationUserStatusType.Revoked },
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[OrganizationUser_SetStatusForUsersByGuidIdArray]
|
||||||
|
@OrganizationUserIds AS [dbo].[GuidIdArray] READONLY,
|
||||||
|
@Status SMALLINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
UPDATE OU
|
||||||
|
SET OU.[Status] = @Status
|
||||||
|
FROM [dbo].[OrganizationUser] OU
|
||||||
|
INNER JOIN @OrganizationUserIds OUI ON OUI.[Id] = OU.[Id]
|
||||||
|
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @OrganizationUserIds
|
||||||
|
END
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using System.Text.Json;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -9,7 +10,9 @@ using Bit.Core.Services;
|
|||||||
using Bit.Core.Test.AutoFixture.CipherFixtures;
|
using Bit.Core.Test.AutoFixture.CipherFixtures;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
using Bit.Core.Vault.Enums;
|
||||||
using Bit.Core.Vault.Models.Data;
|
using Bit.Core.Vault.Models.Data;
|
||||||
|
using Bit.Core.Vault.Queries;
|
||||||
using Bit.Core.Vault.Repositories;
|
using Bit.Core.Vault.Repositories;
|
||||||
using Bit.Core.Vault.Services;
|
using Bit.Core.Vault.Services;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
@ -797,6 +800,233 @@ public class CipherServiceTests
|
|||||||
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SaveDetailsAsyncDependencies
|
||||||
|
{
|
||||||
|
public CipherDetails CipherDetails { get; set; }
|
||||||
|
public SutProvider<CipherService> SutProvider { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SaveDetailsAsyncDependencies GetSaveDetailsAsyncDependencies(
|
||||||
|
SutProvider<CipherService> sutProvider,
|
||||||
|
string newPassword,
|
||||||
|
bool viewPassword,
|
||||||
|
bool editPermission,
|
||||||
|
string? key = null,
|
||||||
|
string? totp = null,
|
||||||
|
CipherLoginFido2CredentialData[]? passkeys = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var cipherDetails = new CipherDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
OrganizationId = Guid.NewGuid(),
|
||||||
|
Type = CipherType.Login,
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
RevisionDate = DateTime.UtcNow,
|
||||||
|
Key = key,
|
||||||
|
};
|
||||||
|
|
||||||
|
var newLoginData = new CipherLoginData { Username = "user", Password = newPassword, Totp = totp, Fido2Credentials = passkeys };
|
||||||
|
cipherDetails.Data = JsonSerializer.Serialize(newLoginData);
|
||||||
|
|
||||||
|
var existingCipher = new Cipher
|
||||||
|
{
|
||||||
|
Id = cipherDetails.Id,
|
||||||
|
Data = JsonSerializer.Serialize(
|
||||||
|
new CipherLoginData
|
||||||
|
{
|
||||||
|
Username = "user",
|
||||||
|
Password = "OriginalPassword",
|
||||||
|
Totp = "OriginalTotp",
|
||||||
|
Fido2Credentials = []
|
||||||
|
}
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICipherRepository>()
|
||||||
|
.GetByIdAsync(cipherDetails.Id)
|
||||||
|
.Returns(existingCipher);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICipherRepository>()
|
||||||
|
.ReplaceAsync(Arg.Any<CipherDetails>())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var permissions = new Dictionary<Guid, OrganizationCipherPermission>
|
||||||
|
{
|
||||||
|
{ cipherDetails.Id, new OrganizationCipherPermission { ViewPassword = viewPassword, Edit = editPermission } }
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IGetCipherPermissionsForUserQuery>()
|
||||||
|
.GetByOrganization(cipherDetails.OrganizationId.Value)
|
||||||
|
.Returns(permissions);
|
||||||
|
|
||||||
|
return new SaveDetailsAsyncDependencies
|
||||||
|
{
|
||||||
|
CipherDetails = cipherDetails,
|
||||||
|
SutProvider = sutProvider,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_PasswordNotChangedWithoutViewPasswordPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: false, editPermission: true);
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Equal("OriginalPassword", updatedLoginData.Password);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_PasswordNotChangedWithoutEditPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false);
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Equal("OriginalPassword", updatedLoginData.Password);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_PasswordChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true);
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Equal("NewPassword", updatedLoginData.Password);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_CipherKeyChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true, "NewKey");
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
Assert.Equal("NewKey", deps.CipherDetails.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_CipherKeyChangedWithoutPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false, "NewKey");
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true));
|
||||||
|
|
||||||
|
Assert.Contains("do not have permission", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_TotpChangedWithoutPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false, totp: "NewTotp");
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Equal("OriginalTotp", updatedLoginData.Totp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_TotpChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true, totp: "NewTotp");
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Equal("NewTotp", updatedLoginData.Totp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_Fido2CredentialsChangedWithoutPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var passkeys = new[]
|
||||||
|
{
|
||||||
|
new CipherLoginFido2CredentialData
|
||||||
|
{
|
||||||
|
CredentialId = "CredentialId",
|
||||||
|
UserHandle = "UserHandle",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false, passkeys: passkeys);
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Empty(updatedLoginData.Fido2Credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveDetailsAsync_Fido2CredentialsChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||||
|
{
|
||||||
|
var passkeys = new[]
|
||||||
|
{
|
||||||
|
new CipherLoginFido2CredentialData
|
||||||
|
{
|
||||||
|
CredentialId = "CredentialId",
|
||||||
|
UserHandle = "UserHandle",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true, passkeys: passkeys);
|
||||||
|
|
||||||
|
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||||
|
deps.CipherDetails,
|
||||||
|
deps.CipherDetails.UserId.Value,
|
||||||
|
deps.CipherDetails.RevisionDate,
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||||
|
Assert.Equal(passkeys.Length, updatedLoginData.Fido2Credentials.Length);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task DeleteAsync_WithPersonalCipherOwner_DeletesCipher(
|
public async Task DeleteAsync_WithPersonalCipherOwner_DeletesCipher(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM bitwarden/server:latest
|
FROM ghcr.io/bitwarden/server
|
||||||
|
|
||||||
LABEL com.bitwarden.product="bitwarden"
|
LABEL com.bitwarden.product="bitwarden"
|
||||||
|
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_SetStatusForUsersByGuidIdArray]
|
||||||
|
@OrganizationUserIds AS [dbo].[GuidIdArray] READONLY,
|
||||||
|
@Status SMALLINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
UPDATE OU
|
||||||
|
SET OU.[Status] = @Status
|
||||||
|
FROM [dbo].[OrganizationUser] OU
|
||||||
|
INNER JOIN @OrganizationUserIds OUI ON OUI.[Id] = OU.[Id]
|
||||||
|
|
||||||
|
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @OrganizationUserIds
|
||||||
|
END
|
||||||
|
GO
|
Loading…
x
Reference in New Issue
Block a user