1
0
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:
Oscar Hinton
2020-12-16 20:36:47 +01:00
committed by GitHub
parent 9bb63b86f0
commit 0f1af2333e
60 changed files with 2073 additions and 3 deletions

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

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

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

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

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Models.Mail
{
public class EmergencyAccessAcceptedViewModel : BaseMailModel
{
public string GranteeEmail { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Models.Mail
{
public class EmergencyAccessApprovedViewModel : BaseMailModel
{
public string Name { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Models.Mail
{
public class EmergencyAccessConfirmedViewModel : BaseMailModel
{
public string Name { get; set; }
}
}

View 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}";
}
}

View File

@ -0,0 +1,8 @@
namespace Bit.Core.Models.Mail
{
public class EmergencyAccessRecoveryTimedOutViewModel : BaseMailModel
{
public string Name { get; set; }
public string Action { get; set; }
}
}

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

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Models.Mail
{
public class EmergencyAccessRejectedViewModel : BaseMailModel
{
public string Name { get; set; }
}
}

View 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,
};
}
}
}