1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-17 15:40:59 -05:00

[PM-1188] Server owner auth migration (#2825)

* [PM-1188] add sso project to auth

* [PM-1188] move sso api models to auth

* [PM-1188] fix sso api model namespace & imports

* [PM-1188] move core files to auth

* [PM-1188] fix core sso namespace & models

* [PM-1188] move sso repository files to auth

* [PM-1188] fix sso repo files namespace & imports

* [PM-1188] move sso sql files to auth folder

* [PM-1188] move sso test files to auth folders

* [PM-1188] fix sso tests namespace & imports

* [PM-1188] move auth api files to auth folder

* [PM-1188] fix auth api files namespace & imports

* [PM-1188] move auth core files to auth folder

* [PM-1188] fix auth core files namespace & imports

* [PM-1188] move auth email templates to auth folder

* [PM-1188] move auth email folder back into shared directory

* [PM-1188] fix auth email names

* [PM-1188] move auth core models to auth folder

* [PM-1188] fix auth model namespace & imports

* [PM-1188] add entire Identity project to auth codeowners

* [PM-1188] fix auth orm files namespace & imports

* [PM-1188] move auth orm files to auth folder

* [PM-1188] move auth sql files to auth folder

* [PM-1188] move auth tests to auth folder

* [PM-1188] fix auth test files namespace & imports

* [PM-1188] move emergency access api files to auth folder

* [PM-1188] fix emergencyaccess api files namespace & imports

* [PM-1188] move emergency access core files to auth folder

* [PM-1188] fix emergency access core files namespace & imports

* [PM-1188] move emergency access orm files to auth folder

* [PM-1188] fix emergency access orm files namespace & imports

* [PM-1188] move emergency access sql files to auth folder

* [PM-1188] move emergencyaccess test files to auth folder

* [PM-1188] fix emergency access test files namespace & imports

* [PM-1188] move captcha files to auth folder

* [PM-1188] fix captcha files namespace & imports

* [PM-1188] move auth admin files into auth folder

* [PM-1188] fix admin auth files namespace & imports
- configure mvc to look in auth folders for views

* [PM-1188] remove extra imports and formatting

* [PM-1188] fix ef auth model imports

* [PM-1188] fix DatabaseContextModelSnapshot paths

* [PM-1188] fix grant import in ef

* [PM-1188] update sqlproj

* [PM-1188] move missed sqlproj files

* [PM-1188] move auth ef models out of auth folder

* [PM-1188] fix auth ef models namespace

* [PM-1188] remove auth ef models unused imports

* [PM-1188] fix imports for auth ef models

* [PM-1188] fix more ef model imports

* [PM-1188] fix file encodings
This commit is contained in:
Jake Fink
2023-04-14 13:25:56 -04:00
committed by GitHub
parent 2529c5b36f
commit 88dd745070
332 changed files with 704 additions and 522 deletions

View File

@ -0,0 +1,162 @@
using Bit.Api.Auth.Models.Request;
using Bit.Api.Auth.Models.Response;
using Bit.Api.Models.Response;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Exceptions;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Auth.Controllers;
[Route("auth-requests")]
[Authorize("Application")]
public class AuthRequestsController : Controller
{
private readonly IUserRepository _userRepository;
private readonly IDeviceRepository _deviceRepository;
private readonly IUserService _userService;
private readonly IAuthRequestRepository _authRequestRepository;
private readonly ICurrentContext _currentContext;
private readonly IPushNotificationService _pushNotificationService;
private readonly IGlobalSettings _globalSettings;
public AuthRequestsController(
IUserRepository userRepository,
IDeviceRepository deviceRepository,
IUserService userService,
IAuthRequestRepository authRequestRepository,
ICurrentContext currentContext,
IPushNotificationService pushNotificationService,
IGlobalSettings globalSettings)
{
_userRepository = userRepository;
_deviceRepository = deviceRepository;
_userService = userService;
_authRequestRepository = authRequestRepository;
_currentContext = currentContext;
_pushNotificationService = pushNotificationService;
_globalSettings = globalSettings;
}
[HttpGet("")]
public async Task<ListResponseModel<AuthRequestResponseModel>> Get()
{
var userId = _userService.GetProperUserId(User).Value;
var authRequests = await _authRequestRepository.GetManyByUserIdAsync(userId);
var responses = authRequests.Select(a => new AuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault)).ToList();
return new ListResponseModel<AuthRequestResponseModel>(responses);
}
[HttpGet("{id}")]
public async Task<AuthRequestResponseModel> Get(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var authRequest = await _authRequestRepository.GetByIdAsync(new Guid(id));
if (authRequest == null || authRequest.UserId != userId)
{
throw new NotFoundException();
}
return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
}
[HttpGet("{id}/response")]
[AllowAnonymous]
public async Task<AuthRequestResponseModel> GetResponse(string id, [FromQuery] string code)
{
var authRequest = await _authRequestRepository.GetByIdAsync(new Guid(id));
if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, code) || authRequest.GetExpirationDate() < DateTime.UtcNow)
{
throw new NotFoundException();
}
return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
}
[HttpPost("")]
[AllowAnonymous]
public async Task<AuthRequestResponseModel> Post([FromBody] AuthRequestCreateRequestModel model)
{
var user = await _userRepository.GetByEmailAsync(model.Email);
if (user == null)
{
throw new NotFoundException();
}
if (!_currentContext.DeviceType.HasValue)
{
throw new BadRequestException("Device type not provided.");
}
if (_globalSettings.PasswordlessAuth.KnownDevicesOnly)
{
var devices = await _deviceRepository.GetManyByUserIdAsync(user.Id);
if (devices == null || !devices.Any(d => d.Identifier == model.DeviceIdentifier))
{
throw new BadRequestException("Login with device is only available on devices that have been previously logged in.");
}
}
var authRequest = new AuthRequest
{
RequestDeviceIdentifier = model.DeviceIdentifier,
RequestDeviceType = _currentContext.DeviceType.Value,
RequestIpAddress = _currentContext.IpAddress,
AccessCode = model.AccessCode,
PublicKey = model.PublicKey,
UserId = user.Id,
Type = model.Type.Value
};
authRequest = await _authRequestRepository.CreateAsync(authRequest);
await _pushNotificationService.PushAuthRequestAsync(authRequest);
var r = new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
return r;
}
[HttpPut("{id}")]
public async Task<AuthRequestResponseModel> Put(string id, [FromBody] AuthRequestUpdateRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
var authRequest = await _authRequestRepository.GetByIdAsync(new Guid(id));
if (authRequest == null || authRequest.UserId != userId || authRequest.GetExpirationDate() < DateTime.UtcNow)
{
throw new NotFoundException();
}
if (authRequest.Approved is not null)
{
throw new DuplicateAuthRequestException();
}
var device = await _deviceRepository.GetByIdentifierAsync(model.DeviceIdentifier, userId);
if (device == null)
{
throw new BadRequestException("Invalid device.");
}
authRequest.ResponseDeviceId = device.Id;
authRequest.ResponseDate = DateTime.UtcNow;
authRequest.Approved = model.RequestApproved;
if (model.RequestApproved)
{
authRequest.Key = model.Key;
authRequest.MasterPasswordHash = model.MasterPasswordHash;
}
await _authRequestRepository.ReplaceAsync(authRequest);
// We only want to send an approval notification if the request is approved (or null),
// to not leak that it was denied to the originating client if it was originated by a malicious actor.
if (authRequest.Approved ?? true)
{
await _pushNotificationService.PushAuthRequestResponseAsync(authRequest);
}
return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
}
}

View File

@ -0,0 +1,180 @@
using Bit.Api.Auth.Models.Request;
using Bit.Api.Auth.Models.Response;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response;
using Bit.Api.Vault.Models.Response;
using Bit.Core.Auth.Services;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Auth.Controllers;
[Route("emergency-access")]
[Authorize("Application")]
public class EmergencyAccessController : Controller
{
private readonly IUserService _userService;
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
private readonly IEmergencyAccessService _emergencyAccessService;
private readonly IGlobalSettings _globalSettings;
public EmergencyAccessController(
IUserService userService,
IEmergencyAccessRepository emergencyAccessRepository,
IEmergencyAccessService emergencyAccessService,
IGlobalSettings globalSettings)
{
_userService = userService;
_emergencyAccessRepository = emergencyAccessRepository;
_emergencyAccessService = emergencyAccessService;
_globalSettings = globalSettings;
}
[HttpGet("trusted")]
public async Task<ListResponseModel<EmergencyAccessGranteeDetailsResponseModel>> GetContacts()
{
var userId = _userService.GetProperUserId(User);
var granteeDetails = await _emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(userId.Value);
var responses = granteeDetails.Select(d =>
new EmergencyAccessGranteeDetailsResponseModel(d));
return new ListResponseModel<EmergencyAccessGranteeDetailsResponseModel>(responses);
}
[HttpGet("granted")]
public async Task<ListResponseModel<EmergencyAccessGrantorDetailsResponseModel>> GetGrantees()
{
var userId = _userService.GetProperUserId(User);
var granteeDetails = await _emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(userId.Value);
var responses = granteeDetails.Select(d => new EmergencyAccessGrantorDetailsResponseModel(d));
return new ListResponseModel<EmergencyAccessGrantorDetailsResponseModel>(responses);
}
[HttpGet("{id}")]
public async Task<EmergencyAccessGranteeDetailsResponseModel> Get(Guid id)
{
var userId = _userService.GetProperUserId(User);
var result = await _emergencyAccessService.GetAsync(id, userId.Value);
return new EmergencyAccessGranteeDetailsResponseModel(result);
}
[HttpGet("{id}/policies")]
public async Task<ListResponseModel<PolicyResponseModel>> Policies(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var policies = await _emergencyAccessService.GetPoliciesAsync(id, user);
var responses = policies.Select<Policy, PolicyResponseModel>(policy => new PolicyResponseModel(policy));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(Guid id, [FromBody] EmergencyAccessUpdateRequestModel model)
{
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
if (emergencyAccess == null)
{
throw new NotFoundException();
}
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.SaveAsync(model.ToEmergencyAccess(emergencyAccess), user);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id)
{
var userId = _userService.GetProperUserId(User);
await _emergencyAccessService.DeleteAsync(id, userId.Value);
}
[HttpPost("invite")]
public async Task Invite([FromBody] EmergencyAccessInviteRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.InviteAsync(user, model.Email, model.Type.Value, model.WaitTimeDays);
}
[HttpPost("{id}/reinvite")]
public async Task Reinvite(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.ResendInviteAsync(user, id);
}
[HttpPost("{id}/accept")]
public async Task Accept(Guid id, [FromBody] OrganizationUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.AcceptUserAsync(id, user, model.Token, _userService);
}
[HttpPost("{id}/confirm")]
public async Task Confirm(Guid id, [FromBody] OrganizationUserConfirmRequestModel model)
{
var userId = _userService.GetProperUserId(User);
await _emergencyAccessService.ConfirmUserAsync(id, model.Key, userId.Value);
}
[HttpPost("{id}/initiate")]
public async Task Initiate(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.InitiateAsync(id, user);
}
[HttpPost("{id}/approve")]
public async Task Accept(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.ApproveAsync(id, user);
}
[HttpPost("{id}/reject")]
public async Task Reject(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.RejectAsync(id, user);
}
[HttpPost("{id}/takeover")]
public async Task<EmergencyAccessTakeoverResponseModel> Takeover(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var (result, grantor) = await _emergencyAccessService.TakeoverAsync(id, user);
return new EmergencyAccessTakeoverResponseModel(result, grantor);
}
[HttpPost("{id}/password")]
public async Task Password(Guid id, [FromBody] EmergencyAccessPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
await _emergencyAccessService.PasswordAsync(id, user, model.NewMasterPasswordHash, model.Key);
}
[HttpPost("{id}/view")]
public async Task<EmergencyAccessViewResponseModel> ViewCiphers(Guid id)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var viewResult = await _emergencyAccessService.ViewAsync(id, user);
return new EmergencyAccessViewResponseModel(_globalSettings, viewResult.EmergencyAccess, viewResult.Ciphers);
}
[HttpGet("{id}/{cipherId}/attachment/{attachmentId}")]
public async Task<AttachmentResponseModel> GetAttachmentData(Guid id, Guid cipherId, string attachmentId)
{
var user = await _userService.GetUserByPrincipalAsync(User);
var result =
await _emergencyAccessService.GetAttachmentDownloadAsync(id, cipherId, attachmentId, user);
return new AttachmentResponseModel(result);
}
}

View File

@ -0,0 +1,441 @@
using Bit.Api.Auth.Models.Request;
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.Auth.Models.Response.TwoFactor;
using Bit.Api.Models.Request;
using Bit.Api.Models.Response;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.LoginFeatures.PasswordlessLogin.Interfaces;
using Bit.Core.Auth.Utilities;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Fido2NetLib;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Auth.Controllers;
[Route("two-factor")]
[Authorize("Web")]
public class TwoFactorController : Controller
{
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly GlobalSettings _globalSettings;
private readonly UserManager<User> _userManager;
private readonly ICurrentContext _currentContext;
private readonly IVerifyAuthRequestCommand _verifyAuthRequestCommand;
public TwoFactorController(
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
GlobalSettings globalSettings,
UserManager<User> userManager,
ICurrentContext currentContext,
IVerifyAuthRequestCommand verifyAuthRequestCommand)
{
_userService = userService;
_organizationRepository = organizationRepository;
_organizationService = organizationService;
_globalSettings = globalSettings;
_userManager = userManager;
_currentContext = currentContext;
_verifyAuthRequestCommand = verifyAuthRequestCommand;
}
[HttpGet("")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> Get()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var providers = user.GetTwoFactorProviders()?.Select(
p => new TwoFactorProviderResponseModel(p.Key, p.Value));
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
}
[HttpGet("~/organizations/{id}/two-factor")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> GetOrganization(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var providers = organization.GetTwoFactorProviders()?.Select(
p => new TwoFactorProviderResponseModel(p.Key, p.Value));
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
}
[HttpPost("get-authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPut("authenticator")]
[HttpPost("authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> PutAuthenticator(
[FromBody] UpdateTwoFactorAuthenticatorRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))
{
await Task.Delay(2000);
throw new BadRequestException("Token", "Invalid token.");
}
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Authenticator);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPost("get-yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> GetYubiKey([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPut("yubikey")]
[HttpPost("yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> PutYubiKey([FromBody] UpdateTwoFactorYubicoOtpRequestModel model)
{
var user = await CheckAsync(model, true);
model.ToUser(user);
await ValidateYubiKeyAsync(user, nameof(model.Key1), model.Key1);
await ValidateYubiKeyAsync(user, nameof(model.Key2), model.Key2);
await ValidateYubiKeyAsync(user, nameof(model.Key3), model.Key3);
await ValidateYubiKeyAsync(user, nameof(model.Key4), model.Key4);
await ValidateYubiKeyAsync(user, nameof(model.Key5), model.Key5);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.YubiKey);
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPost("get-duo")]
public async Task<TwoFactorDuoResponseModel> GetDuo([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPut("duo")]
[HttpPost("duo")]
public async Task<TwoFactorDuoResponseModel> PutDuo([FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, true);
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
await duoApi.JSONApiCall("GET", "/auth/v2/check");
}
catch (DuoException)
{
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
}
model.ToUser(user);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPost("~/organizations/{id}/two-factor/get-duo")]
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
[FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
[HttpPut("~/organizations/{id}/two-factor/duo")]
[HttpPost("~/organizations/{id}/two-factor/duo")]
public async Task<TwoFactorDuoResponseModel> PutOrganizationDuo(string id,
[FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
await duoApi.JSONApiCall("GET", "/auth/v2/check");
}
catch (DuoException)
{
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
}
model.ToOrganization(organization);
await _organizationService.UpdateTwoFactorProviderAsync(organization,
TwoFactorProviderType.OrganizationDuo);
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
[HttpPost("get-webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpPost("get-webauthn-challenge")]
[ApiExplorerSettings(IgnoreApi = true)] // Disable Swagger due to CredentialCreateOptions not converting properly
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, true);
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
return reg;
}
[HttpPut("webauthn")]
[HttpPost("webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> PutWebAuthn([FromBody] TwoFactorWebAuthnRequestModel model)
{
var user = await CheckAsync(model, true);
var success = await _userService.CompleteWebAuthRegistrationAsync(
user, model.Id.Value, model.Name, model.DeviceResponse);
if (!success)
{
throw new BadRequestException("Unable to complete WebAuthn registration.");
}
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpDelete("webauthn")]
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn([FromBody] TwoFactorWebAuthnDeleteRequestModel model)
{
var user = await CheckAsync(model, true);
await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value);
var response = new TwoFactorWebAuthnResponseModel(user);
return response;
}
[HttpPost("get-email")]
public async Task<TwoFactorEmailResponseModel> GetEmail([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
[HttpPost("send-email")]
public async Task SendEmail([FromBody] TwoFactorEmailRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
await _userService.SendTwoFactorEmailAsync(user);
}
[AllowAnonymous]
[HttpPost("send-email-login")]
public async Task SendEmailLogin([FromBody] TwoFactorEmailRequestModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email.ToLowerInvariant());
if (user != null)
{
// check if 2FA email is from passwordless
if (!string.IsNullOrEmpty(model.AuthRequestAccessCode))
{
if (await _verifyAuthRequestCommand
.VerifyAuthRequestAsync(new Guid(model.AuthRequestId), model.AuthRequestAccessCode))
{
await _userService.SendTwoFactorEmailAsync(user);
return;
}
}
else if (await _userService.VerifySecretAsync(user, model.Secret))
{
await _userService.SendTwoFactorEmailAsync(user);
return;
}
}
await Task.Delay(2000);
throw new BadRequestException("Cannot send two-factor email.");
}
[HttpPut("email")]
[HttpPost("email")]
public async Task<TwoFactorEmailResponseModel> PutEmail([FromBody] UpdateTwoFactorEmailRequestModel model)
{
var user = await CheckAsync(model, false);
model.ToUser(user);
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), model.Token))
{
await Task.Delay(2000);
throw new BadRequestException("Token", "Invalid token.");
}
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
[HttpPut("disable")]
[HttpPost("disable")]
public async Task<TwoFactorProviderResponseModel> PutDisable([FromBody] TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model, false);
await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value, _organizationService);
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
return response;
}
[HttpPut("~/organizations/{id}/two-factor/disable")]
[HttpPost("~/organizations/{id}/two-factor/disable")]
public async Task<TwoFactorProviderResponseModel> PutOrganizationDisable(string id,
[FromBody] TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
await _organizationService.DisableTwoFactorProviderAsync(organization, model.Type.Value);
var response = new TwoFactorProviderResponseModel(model.Type.Value, organization);
return response;
}
[HttpPost("get-recover")]
public async Task<TwoFactorRecoverResponseModel> GetRecover([FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
var response = new TwoFactorRecoverResponseModel(user);
return response;
}
[HttpPost("recover")]
[AllowAnonymous]
public async Task PostRecover([FromBody] TwoFactorRecoveryRequestModel model)
{
if (!await _userService.RecoverTwoFactorAsync(model.Email, model.MasterPasswordHash, model.RecoveryCode,
_organizationService))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "Invalid information. Try again.");
}
}
[Obsolete("Leaving this for backwards compatibilty on clients")]
[HttpGet("get-device-verification-settings")]
public Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
{
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
}
[Obsolete("Leaving this for backwards compatibilty on clients")]
[HttpPut("device-verification-settings")]
public Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
{
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
}
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "User verification failed.");
}
if (premium && !(await _userService.CanAccessPremium(user)))
{
throw new BadRequestException("Premium status is required.");
}
return user;
}
private async Task ValidateYubiKeyAsync(User user, string name, string value)
{
if (string.IsNullOrWhiteSpace(value) || value.Length == 12)
{
return;
}
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
{
await Task.Delay(2000);
throw new BadRequestException(name, $"{name} is invalid.");
}
else
{
await Task.Delay(500);
}
}
}

View File

@ -0,0 +1,23 @@
using Bit.Core.Auth.Services;
using Bit.Core.Jobs;
using Quartz;
namespace Bit.Api.Auth.Jobs;
public class EmergencyAccessNotificationJob : BaseJob
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public EmergencyAccessNotificationJob(IServiceScopeFactory serviceScopeFactory, ILogger<EmergencyAccessNotificationJob> logger)
: base(logger)
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var emergencyAccessService = scope.ServiceProvider.GetService(typeof(IEmergencyAccessService)) as IEmergencyAccessService;
await emergencyAccessService.SendNotificationsAsync();
}
}

View File

@ -0,0 +1,23 @@
using Bit.Core.Auth.Services;
using Bit.Core.Jobs;
using Quartz;
namespace Bit.Api.Auth.Jobs;
public class EmergencyAccessTimeoutJob : BaseJob
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public EmergencyAccessTimeoutJob(IServiceScopeFactory serviceScopeFactory, ILogger<EmergencyAccessNotificationJob> logger)
: base(logger)
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
using var scope = _serviceScopeFactory.CreateScope();
var emergencyAccessService = scope.ServiceProvider.GetService(typeof(IEmergencyAccessService)) as IEmergencyAccessService;
await emergencyAccessService.HandleTimedOutRequestsAsync();
}
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Public;
public abstract class AssociationWithPermissionsBaseModel
{
/// <summary>
/// The associated object's unique identifier.
/// </summary>
/// <example>bfbc8338-e329-4dc0-b0c9-317c2ebf1a09</example>
[Required]
public Guid? Id { get; set; }
/// <summary>
/// When true, the read only permission will not allow the user or group to make changes to items.
/// </summary>
[Required]
public bool? ReadOnly { get; set; }
}

View File

@ -0,0 +1,15 @@
using Bit.Core.Models.Data;
namespace Bit.Api.Auth.Models.Public.Request;
public class AssociationWithPermissionsRequestModel : AssociationWithPermissionsBaseModel
{
public CollectionAccessSelection ToSelectionReadOnly()
{
return new CollectionAccessSelection
{
Id = Id.Value,
ReadOnly = ReadOnly.Value
};
}
}

View File

@ -0,0 +1,16 @@
using Bit.Core.Models.Data;
namespace Bit.Api.Auth.Models.Public.Response;
public class AssociationWithPermissionsResponseModel : AssociationWithPermissionsBaseModel
{
public AssociationWithPermissionsResponseModel(CollectionAccessSelection selection)
{
if (selection == null)
{
throw new ArgumentNullException(nameof(selection));
}
Id = selection.Id;
ReadOnly = selection.ReadOnly;
}
}

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class DeleteRecoverRequestModel
{
[Required]
[EmailAddress]
[StringLength(256)]
public string Email { get; set; }
}

View File

@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class EmailRequestModel : SecretVerificationRequestModel
{
[Required]
[StrictEmailAddress]
[StringLength(256)]
public string NewEmail { get; set; }
[Required]
[StringLength(300)]
public string NewMasterPasswordHash { get; set; }
[Required]
public string Token { get; set; }
[Required]
public string Key { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class EmailTokenRequestModel : SecretVerificationRequestModel
{
[Required]
[StrictEmailAddress]
[StringLength(256)]
public string NewEmail { get; set; }
}

View File

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class KdfRequestModel : PasswordRequestModel, IValidatableObject
{
[Required]
public KdfType? Kdf { get; set; }
[Required]
public int? KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Kdf.HasValue && KdfIterations.HasValue)
{
return KdfSettingsValidator.Validate(Kdf.Value, KdfIterations.Value, KdfMemory, KdfParallelism);
}
return Enumerable.Empty<ValidationResult>();
}
}

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class PasswordHintRequestModel
{
[Required]
[EmailAddress]
[StringLength(256)]
public string Email { get; set; }
}

View File

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class PasswordRequestModel : SecretVerificationRequestModel
{
[Required]
[StringLength(300)]
public string NewMasterPasswordHash { get; set; }
[StringLength(50)]
public string MasterPasswordHint { get; set; }
[Required]
public string Key { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class RegenerateTwoFactorRequestModel
{
[Required]
public string MasterPasswordHash { get; set; }
[Required]
[StringLength(50)]
public string Token { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class SecretVerificationRequestModel : IValidatableObject
{
[StringLength(300)]
public string MasterPasswordHash { get; set; }
public string OTP { get; set; }
public string AuthRequestAccessCode { get; set; }
public string Secret => !string.IsNullOrEmpty(MasterPasswordHash) ? MasterPasswordHash : OTP;
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(Secret) && string.IsNullOrEmpty(AuthRequestAccessCode))
{
yield return new ValidationResult("MasterPasswordHash, OTP or AccessCode must be supplied.");
}
}
}

View File

@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class SetKeyConnectorKeyRequestModel : IValidatableObject
{
[Required]
public string Key { get; set; }
[Required]
public KeysRequestModel Keys { get; set; }
[Required]
public KdfType Kdf { get; set; }
[Required]
public int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
[Required]
public string OrgIdentifier { get; set; }
public User ToUser(User existingUser)
{
existingUser.Kdf = Kdf;
existingUser.KdfIterations = KdfIterations;
existingUser.KdfMemory = KdfMemory;
existingUser.KdfParallelism = KdfParallelism;
existingUser.Key = Key;
Keys.ToUser(existingUser);
return existingUser;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return KdfSettingsValidator.Validate(Kdf, KdfIterations, KdfMemory, KdfParallelism);
}
}

View File

@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class SetPasswordRequestModel : IValidatableObject
{
[Required]
[StringLength(300)]
public string MasterPasswordHash { get; set; }
[Required]
public string Key { get; set; }
[StringLength(50)]
public string MasterPasswordHint { get; set; }
[Required]
public KeysRequestModel Keys { get; set; }
[Required]
public KdfType Kdf { get; set; }
[Required]
public int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
public string OrgIdentifier { get; set; }
public User ToUser(User existingUser)
{
existingUser.MasterPasswordHint = MasterPasswordHint;
existingUser.Kdf = Kdf;
existingUser.KdfIterations = KdfIterations;
existingUser.KdfMemory = KdfMemory;
existingUser.KdfParallelism = KdfParallelism;
existingUser.Key = Key;
Keys.ToUser(existingUser);
return existingUser;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return KdfSettingsValidator.Validate(Kdf, KdfIterations, KdfMemory, KdfParallelism);
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Bit.Api.Models.Request;
using Bit.Api.Vault.Models.Request;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class UpdateKeyRequestModel
{
[Required]
[StringLength(300)]
public string MasterPasswordHash { get; set; }
[Required]
public IEnumerable<CipherWithIdRequestModel> Ciphers { get; set; }
[Required]
public IEnumerable<FolderWithIdRequestModel> Folders { get; set; }
public IEnumerable<SendWithIdRequestModel> Sends { get; set; }
[Required]
public string PrivateKey { get; set; }
[Required]
public string Key { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class UpdateProfileRequestModel
{
[StringLength(50)]
public string Name { get; set; }
[StringLength(50)]
[Obsolete("Changes will be made via the 'password' endpoint going forward.")]
public string MasterPasswordHint { get; set; }
public User ToUser(User existingUser)
{
existingUser.Name = Name;
existingUser.MasterPasswordHint = string.IsNullOrWhiteSpace(MasterPasswordHint) ? null : MasterPasswordHint;
return existingUser;
}
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
using Bit.Api.Models.Request.Organizations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class UpdateTempPasswordRequestModel : OrganizationUserResetPasswordRequestModel
{
[StringLength(50)]
public string MasterPasswordHint { get; set; }
}

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class VerifyDeleteRecoverRequestModel
{
[Required]
public string UserId { get; set; }
[Required]
public string Token { get; set; }
}

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class VerifyEmailRequestModel
{
[Required]
public string UserId { get; set; }
[Required]
public string Token { get; set; }
}

View File

@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Auth.Models.Request.Accounts;
public class VerifyOTPRequestModel
{
[Required]
public string OTP { get; set; }
}

View File

@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Auth.Enums;
using Newtonsoft.Json;
namespace Bit.Api.Auth.Models.Request;
public class AuthRequestCreateRequestModel
{
[Required]
public string Email { get; set; }
[Required]
public string PublicKey { get; set; }
[Required]
public string DeviceIdentifier { get; set; }
[Required]
[StringLength(25)]
public string AccessCode { get; set; }
[Required]
public AuthRequestType? Type { get; set; }
}
public class AuthRequestUpdateRequestModel
{
public string Key { get; set; }
public string MasterPasswordHash { get; set; }
[Required]
public string DeviceIdentifier { get; set; }
[Required]
public bool RequestApproved { get; set; }
}

View File

@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Utilities;
namespace Bit.Api.Auth.Models.Request;
public class EmergencyAccessInviteRequestModel
{
[Required]
[StrictEmailAddress]
[StringLength(256)]
public string Email { get; set; }
[Required]
public EmergencyAccessType? Type { get; set; }
[Required]
public int WaitTimeDays { get; set; }
}
public class EmergencyAccessUpdateRequestModel
{
[Required]
public 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,226 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Services;
using Bit.Core.Sso;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
namespace Bit.Api.Auth.Models.Request.Organizations;
public class OrganizationSsoRequestModel
{
[Required]
public bool Enabled { get; set; }
[StringLength(50)]
public string Identifier { get; set; }
[Required]
public SsoConfigurationDataRequest Data { get; set; }
public SsoConfig ToSsoConfig(Guid organizationId)
{
return ToSsoConfig(new SsoConfig { OrganizationId = organizationId });
}
public SsoConfig ToSsoConfig(SsoConfig existingConfig)
{
existingConfig.Enabled = Enabled;
var configurationData = Data.ToConfigurationData();
existingConfig.SetData(configurationData);
return existingConfig;
}
}
public class SsoConfigurationDataRequest : IValidatableObject
{
public SsoConfigurationDataRequest() { }
[Required]
public SsoType ConfigType { get; set; }
public bool KeyConnectorEnabled { get; set; }
public string KeyConnectorUrl { get; set; }
// OIDC
public string Authority { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string MetadataAddress { get; set; }
public OpenIdConnectRedirectBehavior RedirectBehavior { get; set; }
public bool? GetClaimsFromUserInfoEndpoint { get; set; }
public string AdditionalScopes { get; set; }
public string AdditionalUserIdClaimTypes { get; set; }
public string AdditionalEmailClaimTypes { get; set; }
public string AdditionalNameClaimTypes { get; set; }
public string AcrValues { get; set; }
public string ExpectedReturnAcrValue { get; set; }
// SAML2 SP
public Saml2NameIdFormat SpNameIdFormat { get; set; }
public string SpOutboundSigningAlgorithm { get; set; }
public Saml2SigningBehavior SpSigningBehavior { get; set; }
public bool? SpWantAssertionsSigned { get; set; }
public bool? SpValidateCertificates { get; set; }
public string SpMinIncomingSigningAlgorithm { get; set; }
// SAML2 IDP
public string IdpEntityId { get; set; }
public Saml2BindingType IdpBindingType { get; set; }
public string IdpSingleSignOnServiceUrl { get; set; }
public string IdpSingleLogoutServiceUrl { get; set; }
public string IdpArtifactResolutionServiceUrl { get => null; set { /*IGNORE*/ } }
public string IdpX509PublicCert { get; set; }
public string IdpOutboundSigningAlgorithm { get; set; }
public bool? IdpAllowUnsolicitedAuthnResponse { get; set; }
public bool? IdpDisableOutboundLogoutRequests { get; set; }
public bool? IdpWantAuthnRequestsSigned { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
var i18nService = context.GetService(typeof(II18nService)) as I18nService;
if (ConfigType == SsoType.OpenIdConnect)
{
if (string.IsNullOrWhiteSpace(Authority))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("AuthorityValidationError"),
new[] { nameof(Authority) });
}
if (string.IsNullOrWhiteSpace(ClientId))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("ClientIdValidationError"),
new[] { nameof(ClientId) });
}
if (string.IsNullOrWhiteSpace(ClientSecret))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("ClientSecretValidationError"),
new[] { nameof(ClientSecret) });
}
}
else if (ConfigType == SsoType.Saml2)
{
if (string.IsNullOrWhiteSpace(IdpEntityId))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpEntityIdValidationError"),
new[] { nameof(IdpEntityId) });
}
if (!Uri.IsWellFormedUriString(IdpEntityId, UriKind.Absolute) && string.IsNullOrWhiteSpace(IdpSingleSignOnServiceUrl))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlValidationError"),
new[] { nameof(IdpSingleSignOnServiceUrl) });
}
if (InvalidServiceUrl(IdpSingleSignOnServiceUrl))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlInvalid"),
new[] { nameof(IdpSingleSignOnServiceUrl) });
}
if (InvalidServiceUrl(IdpSingleLogoutServiceUrl))
{
yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleLogoutServiceUrlInvalid"),
new[] { nameof(IdpSingleLogoutServiceUrl) });
}
if (!string.IsNullOrWhiteSpace(IdpX509PublicCert))
{
// Validate the certificate is in a valid format
ValidationResult failedResult = null;
try
{
var certData = CoreHelpers.Base64UrlDecode(StripPemCertificateElements(IdpX509PublicCert));
new X509Certificate2(certData);
}
catch (FormatException)
{
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertInvalidFormatValidationError"),
new[] { nameof(IdpX509PublicCert) });
}
catch (CryptographicException cryptoEx)
{
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertCryptographicExceptionValidationError", cryptoEx.Message),
new[] { nameof(IdpX509PublicCert) });
}
catch (Exception ex)
{
failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertValidationError", ex.Message),
new[] { nameof(IdpX509PublicCert) });
}
if (failedResult != null)
{
yield return failedResult;
}
}
}
}
public SsoConfigurationData ToConfigurationData()
{
return new SsoConfigurationData
{
ConfigType = ConfigType,
KeyConnectorEnabled = KeyConnectorEnabled,
KeyConnectorUrl = KeyConnectorUrl,
Authority = Authority,
ClientId = ClientId,
ClientSecret = ClientSecret,
MetadataAddress = MetadataAddress,
GetClaimsFromUserInfoEndpoint = GetClaimsFromUserInfoEndpoint.GetValueOrDefault(),
RedirectBehavior = RedirectBehavior,
IdpEntityId = IdpEntityId,
IdpBindingType = IdpBindingType,
IdpSingleSignOnServiceUrl = IdpSingleSignOnServiceUrl,
IdpSingleLogoutServiceUrl = IdpSingleLogoutServiceUrl,
IdpArtifactResolutionServiceUrl = null,
IdpX509PublicCert = StripPemCertificateElements(IdpX509PublicCert),
IdpOutboundSigningAlgorithm = IdpOutboundSigningAlgorithm,
IdpAllowUnsolicitedAuthnResponse = IdpAllowUnsolicitedAuthnResponse.GetValueOrDefault(),
IdpDisableOutboundLogoutRequests = IdpDisableOutboundLogoutRequests.GetValueOrDefault(),
IdpWantAuthnRequestsSigned = IdpWantAuthnRequestsSigned.GetValueOrDefault(),
SpNameIdFormat = SpNameIdFormat,
SpOutboundSigningAlgorithm = SpOutboundSigningAlgorithm ?? SamlSigningAlgorithms.Sha256,
SpSigningBehavior = SpSigningBehavior,
SpWantAssertionsSigned = SpWantAssertionsSigned.GetValueOrDefault(),
SpValidateCertificates = SpValidateCertificates.GetValueOrDefault(),
SpMinIncomingSigningAlgorithm = SpMinIncomingSigningAlgorithm,
AdditionalScopes = AdditionalScopes,
AdditionalUserIdClaimTypes = AdditionalUserIdClaimTypes,
AdditionalEmailClaimTypes = AdditionalEmailClaimTypes,
AdditionalNameClaimTypes = AdditionalNameClaimTypes,
AcrValues = AcrValues,
ExpectedReturnAcrValue = ExpectedReturnAcrValue,
};
}
private string StripPemCertificateElements(string certificateText)
{
if (string.IsNullOrWhiteSpace(certificateText))
{
return null;
}
return Regex.Replace(certificateText,
@"(((BEGIN|END) CERTIFICATE)|([\-\n\r\t\s\f]))",
string.Empty,
RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
private bool InvalidServiceUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return false;
}
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
{
return true;
}
return Regex.IsMatch(url, "[<>\"]");
}
}

View File

@ -0,0 +1,274 @@
using System.ComponentModel.DataAnnotations;
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.Utilities;
using Bit.Core.Entities;
using Fido2NetLib;
namespace Bit.Api.Auth.Models.Request;
public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationRequestModel
{
[Required]
[StringLength(50)]
public string Token { get; set; }
[Required]
[StringLength(50)]
public string Key { get; set; }
public User ToUser(User extistingUser)
{
var providers = extistingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.Authenticator))
{
providers.Remove(TwoFactorProviderType.Authenticator);
}
providers.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Key"] = Key },
Enabled = true
});
extistingUser.SetTwoFactorProviders(providers);
return extistingUser;
}
}
public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IValidatableObject
{
[Required]
[StringLength(50)]
public string IntegrationKey { get; set; }
[Required]
[StringLength(50)]
public string SecretKey { get; set; }
[Required]
[StringLength(50)]
public string Host { get; set; }
public User ToUser(User extistingUser)
{
var providers = extistingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.Duo))
{
providers.Remove(TwoFactorProviderType.Duo);
}
providers.Add(TwoFactorProviderType.Duo, new TwoFactorProvider
{
MetaData = new Dictionary<string, object>
{
["SKey"] = SecretKey,
["IKey"] = IntegrationKey,
["Host"] = Host
},
Enabled = true
});
extistingUser.SetTwoFactorProviders(providers);
return extistingUser;
}
public Organization ToOrganization(Organization extistingOrg)
{
var providers = extistingOrg.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.OrganizationDuo))
{
providers.Remove(TwoFactorProviderType.OrganizationDuo);
}
providers.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider
{
MetaData = new Dictionary<string, object>
{
["SKey"] = SecretKey,
["IKey"] = IntegrationKey,
["Host"] = Host
},
Enabled = true
});
extistingOrg.SetTwoFactorProviders(providers);
return extistingOrg;
}
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!DuoApi.ValidHost(Host))
{
yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) });
}
}
}
public class UpdateTwoFactorYubicoOtpRequestModel : SecretVerificationRequestModel, IValidatableObject
{
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Key3 { get; set; }
public string Key4 { get; set; }
public string Key5 { get; set; }
[Required]
public bool? Nfc { get; set; }
public User ToUser(User extistingUser)
{
var providers = extistingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.YubiKey))
{
providers.Remove(TwoFactorProviderType.YubiKey);
}
providers.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider
{
MetaData = new Dictionary<string, object>
{
["Key1"] = FormatKey(Key1),
["Key2"] = FormatKey(Key2),
["Key3"] = FormatKey(Key3),
["Key4"] = FormatKey(Key4),
["Key5"] = FormatKey(Key5),
["Nfc"] = Nfc.Value
},
Enabled = true
});
extistingUser.SetTwoFactorProviders(providers);
return extistingUser;
}
private string FormatKey(string keyValue)
{
if (string.IsNullOrWhiteSpace(keyValue))
{
return null;
}
return keyValue.Substring(0, 12);
}
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Key1) && string.IsNullOrWhiteSpace(Key2) && string.IsNullOrWhiteSpace(Key3) &&
string.IsNullOrWhiteSpace(Key4) && string.IsNullOrWhiteSpace(Key5))
{
yield return new ValidationResult("A key is required.", new string[] { nameof(Key1) });
}
if (!string.IsNullOrWhiteSpace(Key1) && Key1.Length < 12)
{
yield return new ValidationResult("Key 1 in invalid.", new string[] { nameof(Key1) });
}
if (!string.IsNullOrWhiteSpace(Key2) && Key2.Length < 12)
{
yield return new ValidationResult("Key 2 in invalid.", new string[] { nameof(Key2) });
}
if (!string.IsNullOrWhiteSpace(Key3) && Key3.Length < 12)
{
yield return new ValidationResult("Key 3 in invalid.", new string[] { nameof(Key3) });
}
if (!string.IsNullOrWhiteSpace(Key4) && Key4.Length < 12)
{
yield return new ValidationResult("Key 4 in invalid.", new string[] { nameof(Key4) });
}
if (!string.IsNullOrWhiteSpace(Key5) && Key5.Length < 12)
{
yield return new ValidationResult("Key 5 in invalid.", new string[] { nameof(Key5) });
}
}
}
public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
{
[Required]
[EmailAddress]
[StringLength(256)]
public string Email { get; set; }
public string AuthRequestId { get; set; }
public User ToUser(User extistingUser)
{
var providers = extistingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.Email))
{
providers.Remove(TwoFactorProviderType.Email);
}
providers.Add(TwoFactorProviderType.Email, new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = Email.ToLowerInvariant() },
Enabled = true
});
extistingUser.SetTwoFactorProviders(providers);
return extistingUser;
}
}
public class TwoFactorWebAuthnRequestModel : TwoFactorWebAuthnDeleteRequestModel
{
[Required]
public AuthenticatorAttestationRawResponse DeviceResponse { get; set; }
public string Name { get; set; }
}
public class TwoFactorWebAuthnDeleteRequestModel : SecretVerificationRequestModel, IValidatableObject
{
[Required]
public int? Id { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (var validationResult in base.Validate(validationContext))
{
yield return validationResult;
}
if (!Id.HasValue || Id < 0 || Id > 5)
{
yield return new ValidationResult("Invalid Key Id", new string[] { nameof(Id) });
}
}
}
public class UpdateTwoFactorEmailRequestModel : TwoFactorEmailRequestModel
{
[Required]
[StringLength(50)]
public string Token { get; set; }
}
public class TwoFactorProviderRequestModel : SecretVerificationRequestModel
{
[Required]
public TwoFactorProviderType? Type { get; set; }
}
public class TwoFactorRecoveryRequestModel : TwoFactorEmailRequestModel
{
[Required]
[StringLength(32)]
public string RecoveryCode { get; set; }
}

View File

@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Bit.Core.Auth.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response;
public class AuthRequestResponseModel : ResponseModel
{
public AuthRequestResponseModel(AuthRequest authRequest, string vaultUri, string obj = "auth-request")
: base(obj)
{
if (authRequest == null)
{
throw new ArgumentNullException(nameof(authRequest));
}
Id = authRequest.Id.ToString();
PublicKey = authRequest.PublicKey;
RequestDeviceType = authRequest.RequestDeviceType.GetType().GetMember(authRequest.RequestDeviceType.ToString())
.FirstOrDefault()?.GetCustomAttribute<DisplayAttribute>()?.GetName();
RequestIpAddress = authRequest.RequestIpAddress;
Key = authRequest.Key;
MasterPasswordHash = authRequest.MasterPasswordHash;
CreationDate = authRequest.CreationDate;
RequestApproved = authRequest.Approved ?? false;
Origin = new Uri(vaultUri).Host;
ResponseDate = authRequest.ResponseDate;
}
public string Id { get; set; }
public string PublicKey { get; set; }
public string RequestDeviceType { get; set; }
public string RequestIpAddress { get; set; }
public string Key { get; set; }
public string MasterPasswordHash { get; set; }
public DateTime CreationDate { get; set; }
public DateTime? ResponseDate { get; set; }
public bool RequestApproved { get; set; }
public string Origin { get; set; }
}

View File

@ -0,0 +1,128 @@
using Bit.Api.Vault.Models.Response;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Settings;
using Bit.Core.Vault.Models.Data;
namespace Bit.Api.Auth.Models.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;
AvatarColor = emergencyAccess.GranteeAvatarColor;
}
public string GranteeId { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public string AvatarColor { 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;
AvatarColor = emergencyAccess.GrantorAvatarColor;
}
public string GrantorId { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public string AvatarColor { 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;
KdfMemory = grantor.KdfMemory;
KdfParallelism = grantor.KdfParallelism;
}
public int KdfIterations { get; private set; }
public int? KdfMemory { get; private set; }
public int? KdfParallelism { get; private set; }
public KdfType Kdf { get; private set; }
public string KeyEncrypted { get; private set; }
}
public class EmergencyAccessViewResponseModel : ResponseModel
{
public EmergencyAccessViewResponseModel(
IGlobalSettings 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,46 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using Bit.Core.Settings;
namespace Bit.Api.Auth.Models.Response.Organizations;
public class OrganizationSsoResponseModel : ResponseModel
{
public OrganizationSsoResponseModel(Organization organization, GlobalSettings globalSettings,
SsoConfig config = null) : base("organizationSso")
{
if (config != null)
{
Enabled = config.Enabled;
Data = config.GetData();
}
Identifier = organization.Identifier;
Urls = new SsoUrls(organization.Id.ToString(), globalSettings);
}
public bool Enabled { get; set; }
public string Identifier { get; set; }
public SsoConfigurationData Data { get; set; }
public SsoUrls Urls { get; set; }
}
public class SsoUrls
{
public SsoUrls(string organizationId, GlobalSettings globalSettings)
{
CallbackPath = SsoConfigurationData.BuildCallbackPath(globalSettings.BaseServiceUri.Sso);
SignedOutCallbackPath = SsoConfigurationData.BuildSignedOutCallbackPath(globalSettings.BaseServiceUri.Sso);
SpEntityId = SsoConfigurationData.BuildSaml2ModulePath(globalSettings.BaseServiceUri.Sso);
SpMetadataUrl = SsoConfigurationData.BuildSaml2MetadataUrl(globalSettings.BaseServiceUri.Sso, organizationId);
SpAcsUrl = SsoConfigurationData.BuildSaml2AcsUrl(globalSettings.BaseServiceUri.Sso, organizationId);
}
public string CallbackPath { get; set; }
public string SignedOutCallbackPath { get; set; }
public string SpEntityId { get; set; }
public string SpMetadataUrl { get; set; }
public string SpAcsUrl { get; set; }
}

View File

@ -0,0 +1,34 @@
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using OtpNet;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorAuthenticatorResponseModel : ResponseModel
{
public TwoFactorAuthenticatorResponseModel(User user)
: base("twoFactorAuthenticator")
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
if (provider?.MetaData?.ContainsKey("Key") ?? false)
{
Key = (string)provider.MetaData["Key"];
Enabled = provider.Enabled;
}
else
{
var key = KeyGeneration.GenerateRandomKey(20);
Key = Base32Encoding.ToString(key);
Enabled = false;
}
}
public bool Enabled { get; set; }
public string Key { get; set; }
}

View File

@ -0,0 +1,65 @@
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorDuoResponseModel : ResponseModel
{
private const string ResponseObj = "twoFactorDuo";
public TwoFactorDuoResponseModel(User user)
: base(ResponseObj)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
Build(provider);
}
public TwoFactorDuoResponseModel(Organization org)
: base(ResponseObj)
{
if (org == null)
{
throw new ArgumentNullException(nameof(org));
}
var provider = org.GetTwoFactorProvider(TwoFactorProviderType.OrganizationDuo);
Build(provider);
}
public bool Enabled { get; set; }
public string Host { get; set; }
public string SecretKey { get; set; }
public string IntegrationKey { get; set; }
private void Build(TwoFactorProvider provider)
{
if (provider?.MetaData != null && provider.MetaData.Count > 0)
{
Enabled = provider.Enabled;
if (provider.MetaData.ContainsKey("Host"))
{
Host = (string)provider.MetaData["Host"];
}
if (provider.MetaData.ContainsKey("SKey"))
{
SecretKey = (string)provider.MetaData["SKey"];
}
if (provider.MetaData.ContainsKey("IKey"))
{
IntegrationKey = (string)provider.MetaData["IKey"];
}
}
else
{
Enabled = false;
}
}
}

View File

@ -0,0 +1,31 @@
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorEmailResponseModel : ResponseModel
{
public TwoFactorEmailResponseModel(User user)
: base("twoFactorEmail")
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
if (provider?.MetaData?.ContainsKey("Email") ?? false)
{
Email = (string)provider.MetaData["Email"];
Enabled = provider.Enabled;
}
else
{
Enabled = false;
}
}
public bool Enabled { get; set; }
public string Email { get; set; }
}

View File

@ -0,0 +1,52 @@
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorProviderResponseModel : ResponseModel
{
private const string ResponseObj = "twoFactorProvider";
public TwoFactorProviderResponseModel(TwoFactorProviderType type, TwoFactorProvider provider)
: base(ResponseObj)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
Enabled = provider.Enabled;
Type = type;
}
public TwoFactorProviderResponseModel(TwoFactorProviderType type, User user)
: base(ResponseObj)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var provider = user.GetTwoFactorProvider(type);
Enabled = provider?.Enabled ?? false;
Type = type;
}
public TwoFactorProviderResponseModel(TwoFactorProviderType type, Organization organization)
: base(ResponseObj)
{
if (organization == null)
{
throw new ArgumentNullException(nameof(organization));
}
var provider = organization.GetTwoFactorProvider(type);
Enabled = provider?.Enabled ?? false;
Type = type;
}
public bool Enabled { get; set; }
public TwoFactorProviderType Type { get; set; }
}

View File

@ -0,0 +1,20 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorRecoverResponseModel : ResponseModel
{
public TwoFactorRecoverResponseModel(User user)
: base("twoFactorRecover")
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Code = user.TwoFactorRecoveryCode;
}
public string Code { get; set; }
}

View File

@ -0,0 +1,41 @@
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorWebAuthnResponseModel : ResponseModel
{
public TwoFactorWebAuthnResponseModel(User user)
: base("twoFactorWebAuthn")
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
Enabled = provider?.Enabled ?? false;
Keys = provider?.MetaData?
.Where(k => k.Key.StartsWith("Key"))
.Select(k => new KeyModel(k.Key, new TwoFactorProvider.WebAuthnData((dynamic)k.Value)));
}
public bool Enabled { get; set; }
public IEnumerable<KeyModel> Keys { get; set; }
public class KeyModel
{
public KeyModel(string id, TwoFactorProvider.WebAuthnData data)
{
Name = data.Name;
Id = Convert.ToInt32(id.Replace("Key", string.Empty));
Migrated = data.Migrated;
}
public string Name { get; set; }
public int Id { get; set; }
public bool Migrated { get; set; }
}
}

View File

@ -0,0 +1,60 @@
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Auth.Models.Response.TwoFactor;
public class TwoFactorYubiKeyResponseModel : ResponseModel
{
public TwoFactorYubiKeyResponseModel(User user)
: base("twoFactorYubiKey")
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey);
if (provider?.MetaData != null && provider.MetaData.Count > 0)
{
Enabled = provider.Enabled;
if (provider.MetaData.ContainsKey("Key1"))
{
Key1 = (string)provider.MetaData["Key1"];
}
if (provider.MetaData.ContainsKey("Key2"))
{
Key2 = (string)provider.MetaData["Key2"];
}
if (provider.MetaData.ContainsKey("Key3"))
{
Key3 = (string)provider.MetaData["Key3"];
}
if (provider.MetaData.ContainsKey("Key4"))
{
Key4 = (string)provider.MetaData["Key4"];
}
if (provider.MetaData.ContainsKey("Key5"))
{
Key5 = (string)provider.MetaData["Key5"];
}
if (provider.MetaData.ContainsKey("Nfc"))
{
Nfc = (bool)provider.MetaData["Nfc"];
}
}
else
{
Enabled = false;
}
}
public bool Enabled { get; set; }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Key3 { get; set; }
public string Key4 { get; set; }
public string Key5 { get; set; }
public bool Nfc { get; set; }
}