mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
Add support for Emergency Access (#1000)
* Add support for Emergency Access * Add migration script * Review comments * Ensure grantor has premium when inviting new grantees. * Resolve review comments * Remove two factor references
This commit is contained in:
47
src/Core/Models/Api/Request/EmergencyAccessRequstModels.cs
Normal file
47
src/Core/Models/Api/Request/EmergencyAccessRequstModels.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Models.Api.Request
|
||||
{
|
||||
public class EmergencyAccessInviteRequestModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[StringLength(50)]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
public Enums.EmergencyAccessType? Type { get; set; }
|
||||
[Required]
|
||||
public int WaitTimeDays { get; set; }
|
||||
}
|
||||
|
||||
public class EmergencyAccessUpdateRequestModel
|
||||
{
|
||||
[Required]
|
||||
public Enums.EmergencyAccessType Type { get; set; }
|
||||
[Required]
|
||||
public int WaitTimeDays { get; set; }
|
||||
public string KeyEncrypted { get; set; }
|
||||
|
||||
public EmergencyAccess ToEmergencyAccess(EmergencyAccess existingEmergencyAccess)
|
||||
{
|
||||
// Ensure we only set keys for a confirmed emergency access.
|
||||
if (!string.IsNullOrWhiteSpace(existingEmergencyAccess.KeyEncrypted) && !string.IsNullOrWhiteSpace(KeyEncrypted))
|
||||
{
|
||||
existingEmergencyAccess.KeyEncrypted = KeyEncrypted;
|
||||
}
|
||||
existingEmergencyAccess.Type = Type;
|
||||
existingEmergencyAccess.WaitTimeDays = WaitTimeDays;
|
||||
return existingEmergencyAccess;
|
||||
}
|
||||
}
|
||||
|
||||
public class EmergencyAccessPasswordRequestModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(300)]
|
||||
public string NewMasterPasswordHash { get; set; }
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
}
|
||||
}
|
119
src/Core/Models/Api/Response/EmergencyAccessResponseModel.cs
Normal file
119
src/Core/Models/Api/Response/EmergencyAccessResponseModel.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Models.Api.Response
|
||||
{
|
||||
public class EmergencyAccessResponseModel : ResponseModel
|
||||
{
|
||||
public EmergencyAccessResponseModel(EmergencyAccess emergencyAccess, string obj = "emergencyAccess") : base(obj)
|
||||
{
|
||||
if (emergencyAccess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(emergencyAccess));
|
||||
}
|
||||
|
||||
Id = emergencyAccess.Id.ToString();
|
||||
Status = emergencyAccess.Status;
|
||||
Type = emergencyAccess.Type;
|
||||
WaitTimeDays = emergencyAccess.WaitTimeDays;
|
||||
}
|
||||
|
||||
public EmergencyAccessResponseModel(EmergencyAccessDetails emergencyAccess, string obj = "emergencyAccess") : base(obj)
|
||||
{
|
||||
if (emergencyAccess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(emergencyAccess));
|
||||
}
|
||||
|
||||
Id = emergencyAccess.Id.ToString();
|
||||
Status = emergencyAccess.Status;
|
||||
Type = emergencyAccess.Type;
|
||||
WaitTimeDays = emergencyAccess.WaitTimeDays;
|
||||
}
|
||||
|
||||
public string Id { get; private set; }
|
||||
public EmergencyAccessStatusType Status { get; private set; }
|
||||
public EmergencyAccessType Type { get; private set; }
|
||||
public int WaitTimeDays { get; private set; }
|
||||
}
|
||||
|
||||
public class EmergencyAccessGranteeDetailsResponseModel : EmergencyAccessResponseModel
|
||||
{
|
||||
public EmergencyAccessGranteeDetailsResponseModel(EmergencyAccessDetails emergencyAccess)
|
||||
: base(emergencyAccess, "emergencyAccessGranteeDetails")
|
||||
{
|
||||
if (emergencyAccess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(emergencyAccess));
|
||||
}
|
||||
|
||||
GranteeId = emergencyAccess.GranteeId.ToString();
|
||||
Email = emergencyAccess.GranteeEmail;
|
||||
Name = emergencyAccess.GranteeName;
|
||||
}
|
||||
|
||||
public string GranteeId { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Email { get; private set; }
|
||||
}
|
||||
|
||||
public class EmergencyAccessGrantorDetailsResponseModel : EmergencyAccessResponseModel
|
||||
{
|
||||
public EmergencyAccessGrantorDetailsResponseModel(EmergencyAccessDetails emergencyAccess)
|
||||
: base(emergencyAccess, "emergencyAccessGrantorDetails")
|
||||
{
|
||||
if (emergencyAccess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(emergencyAccess));
|
||||
}
|
||||
|
||||
GrantorId = emergencyAccess.GrantorId.ToString();
|
||||
Email = emergencyAccess.GrantorEmail;
|
||||
Name = emergencyAccess.GrantorName;
|
||||
}
|
||||
|
||||
public string GrantorId { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Email { get; private set; }
|
||||
}
|
||||
|
||||
public class EmergencyAccessTakeoverResponseModel : ResponseModel
|
||||
{
|
||||
public EmergencyAccessTakeoverResponseModel(EmergencyAccess emergencyAccess, User grantor, string obj = "emergencyAccessTakeover") : base(obj)
|
||||
{
|
||||
if (emergencyAccess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(emergencyAccess));
|
||||
}
|
||||
|
||||
KeyEncrypted = emergencyAccess.KeyEncrypted;
|
||||
Kdf = grantor.Kdf;
|
||||
KdfIterations = grantor.KdfIterations;
|
||||
}
|
||||
|
||||
public int KdfIterations { get; private set; }
|
||||
public KdfType Kdf { get; private set; }
|
||||
public string KeyEncrypted { get; private set; }
|
||||
}
|
||||
|
||||
public class EmergencyAccessViewResponseModel : ResponseModel
|
||||
{
|
||||
public EmergencyAccessViewResponseModel(
|
||||
GlobalSettings globalSettings,
|
||||
EmergencyAccess emergencyAccess,
|
||||
IEnumerable<CipherDetails> ciphers)
|
||||
: base("emergencyAccessView")
|
||||
{
|
||||
KeyEncrypted = emergencyAccess.KeyEncrypted;
|
||||
Ciphers = ciphers.Select(c => new CipherResponseModel(c, globalSettings));
|
||||
}
|
||||
|
||||
public string KeyEncrypted { get; set; }
|
||||
public IEnumerable<CipherResponseModel> Ciphers { get; set; }
|
||||
}
|
||||
}
|
12
src/Core/Models/Data/EmergencyAccessDetails.cs
Normal file
12
src/Core/Models/Data/EmergencyAccessDetails.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class EmergencyAccessDetails : EmergencyAccess
|
||||
{
|
||||
public string GranteeName { get; set; }
|
||||
public string GranteeEmail { get; set; }
|
||||
public string GrantorName { get; set; }
|
||||
public string GrantorEmail { get; set; }
|
||||
}
|
||||
}
|
14
src/Core/Models/Data/EmergencyAccessNotify.cs
Normal file
14
src/Core/Models/Data/EmergencyAccessNotify.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class EmergencyAccessNotify : EmergencyAccess
|
||||
{
|
||||
public string GrantorEmail { get; set; }
|
||||
public string GranteeName { get; set; }
|
||||
}
|
||||
}
|
7
src/Core/Models/Mail/EmergencyAccessAcceptedViewModel.cs
Normal file
7
src/Core/Models/Mail/EmergencyAccessAcceptedViewModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessAcceptedViewModel : BaseMailModel
|
||||
{
|
||||
public string GranteeEmail { get; set; }
|
||||
}
|
||||
}
|
7
src/Core/Models/Mail/EmergencyAccessApprovedViewModel.cs
Normal file
7
src/Core/Models/Mail/EmergencyAccessApprovedViewModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessApprovedViewModel : BaseMailModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessConfirmedViewModel : BaseMailModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
11
src/Core/Models/Mail/EmergencyAccessInvitedViewModel.cs
Normal file
11
src/Core/Models/Mail/EmergencyAccessInvitedViewModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessInvitedViewModel : BaseMailModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string Url => $"{WebVaultUrl}/accept-emergency?id={Id}&name={Name}&email={Email}&token={Token}";
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessRecoveryTimedOutViewModel : BaseMailModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Action { get; set; }
|
||||
}
|
||||
}
|
9
src/Core/Models/Mail/EmergencyAccessRecoveryViewModel.cs
Normal file
9
src/Core/Models/Mail/EmergencyAccessRecoveryViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessRecoveryViewModel : BaseMailModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Action { get; set; }
|
||||
public int DaysLeft { get; set; }
|
||||
}
|
||||
}
|
7
src/Core/Models/Mail/EmergencyAccessRejectedViewModel.cs
Normal file
7
src/Core/Models/Mail/EmergencyAccessRejectedViewModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Models.Mail
|
||||
{
|
||||
public class EmergencyAccessRejectedViewModel : BaseMailModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
46
src/Core/Models/Table/EmergencyAccess.cs
Normal file
46
src/Core/Models/Table/EmergencyAccess.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.Table
|
||||
{
|
||||
public class EmergencyAccess : ITableObject<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid GrantorId { get; set; }
|
||||
public Guid? GranteeId { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string KeyEncrypted { get; set; }
|
||||
public EmergencyAccessType Type { get; set; }
|
||||
public EmergencyAccessStatusType Status { get; set; }
|
||||
public int WaitTimeDays { get; set; }
|
||||
public DateTime? RecoveryInitiatedDate { get; internal set; }
|
||||
public DateTime? LastNotificationDate { get; internal set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
|
||||
public EmergencyAccess ToEmergencyAccess()
|
||||
{
|
||||
return new EmergencyAccess
|
||||
{
|
||||
Id = Id,
|
||||
GrantorId = GrantorId,
|
||||
GranteeId = GranteeId,
|
||||
Email = Email,
|
||||
KeyEncrypted = KeyEncrypted,
|
||||
Type = Type,
|
||||
Status = Status,
|
||||
WaitTimeDays = WaitTimeDays,
|
||||
RecoveryInitiatedDate = RecoveryInitiatedDate,
|
||||
LastNotificationDate = LastNotificationDate,
|
||||
CreationDate = CreationDate,
|
||||
RevisionDate = RevisionDate,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user