Files
server/src/Api/Controllers/TwoFactorController.cs

390 lines
15 KiB
C#
Raw Normal View History

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Bit.Core.Models.Api;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Microsoft.AspNetCore.Identity;
using Bit.Core.Models.Table;
using Bit.Core.Enums;
using System.Linq;
2017-06-22 17:03:35 -04:00
using Bit.Core;
2018-04-02 23:18:26 -04:00
using Bit.Core.Repositories;
2018-12-19 22:27:45 -05:00
using Bit.Core.Utilities;
namespace Bit.Api.Controllers
{
[Route("two-factor")]
2017-06-26 09:09:30 -04:00
[Authorize("Web")]
public class TwoFactorController : Controller
{
private readonly IUserService _userService;
2018-04-02 23:18:26 -04:00
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
2017-06-22 17:03:35 -04:00
private readonly GlobalSettings _globalSettings;
private readonly UserManager<User> _userManager;
2018-04-02 23:18:26 -04:00
private readonly CurrentContext _currentContext;
public TwoFactorController(
IUserService userService,
2018-04-02 23:18:26 -04:00
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
2017-06-22 17:03:35 -04:00
GlobalSettings globalSettings,
2018-04-02 23:18:26 -04:00
UserManager<User> userManager,
CurrentContext currentContext)
{
_userService = userService;
2018-04-02 23:18:26 -04:00
_organizationRepository = organizationRepository;
_organizationService = organizationService;
2017-06-22 17:03:35 -04:00
_globalSettings = globalSettings;
_userManager = userManager;
2018-04-02 23:18:26 -04:00
_currentContext = currentContext;
}
[HttpGet("")]
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> Get()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if(user == null)
{
throw new UnauthorizedAccessException();
}
2018-04-02 23:18:26 -04:00
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(!_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]TwoFactorRequestModel model)
{
2017-07-07 09:28:50 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
var response = new TwoFactorAuthenticatorResponseModel(user);
return response;
}
[HttpPut("authenticator")]
[HttpPost("authenticator")]
public async Task<TwoFactorAuthenticatorResponseModel> PutAuthenticator(
[FromBody]UpdateTwoFactorAuthenticatorRequestModel model)
{
2017-07-07 09:28:50 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
model.ToUser(user);
2018-12-19 22:27:45 -05:00
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;
}
2017-06-20 14:12:31 -04:00
[HttpPost("get-yubikey")]
public async Task<TwoFactorYubiKeyResponseModel> GetYubiKey([FromBody]TwoFactorRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, true);
2017-06-20 14:12:31 -04:00
var response = new TwoFactorYubiKeyResponseModel(user);
return response;
}
[HttpPut("yubikey")]
[HttpPost("yubikey")]
2017-06-21 14:19:07 -04:00
public async Task<TwoFactorYubiKeyResponseModel> PutYubiKey([FromBody]UpdateTwoFactorYubicoOtpRequestModel model)
2017-06-20 14:12:31 -04:00
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, true);
2017-06-20 14:12:31 -04:00
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;
}
2017-06-21 14:19:07 -04:00
[HttpPost("get-duo")]
public async Task<TwoFactorDuoResponseModel> GetDuo([FromBody]TwoFactorRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, true);
2017-06-21 14:19:07 -04:00
var response = new TwoFactorDuoResponseModel(user);
return response;
}
[HttpPut("duo")]
[HttpPost("duo")]
public async Task<TwoFactorDuoResponseModel> PutDuo([FromBody]UpdateTwoFactorDuoRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, true);
2017-06-21 14:19:07 -04:00
model.ToUser(user);
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo);
var response = new TwoFactorDuoResponseModel(user);
return response;
}
2018-04-02 23:18:26 -04:00
[HttpPost("~/organizations/{id}/two-factor/get-duo")]
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
[FromBody]TwoFactorRequestModel model)
{
var user = await CheckAsync(model.MasterPasswordHash, false);
var orgIdGuid = new Guid(id);
if(!_currentContext.OrganizationAdmin(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.MasterPasswordHash, false);
var orgIdGuid = new Guid(id);
if(!_currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if(organization == null)
{
throw new NotFoundException();
}
model.ToOrganization(organization);
await _organizationService.UpdateTwoFactorProviderAsync(organization,
TwoFactorProviderType.OrganizationDuo);
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
2017-06-21 21:46:52 -04:00
[HttpPost("get-u2f")]
public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, true);
2018-10-08 14:38:11 -04:00
var response = new TwoFactorU2fResponseModel(user);
return response;
}
[HttpPost("get-u2f-challenge")]
public async Task<TwoFactorU2fResponseModel.ChallengeModel> GetU2fChallenge(
[FromBody]TwoFactorRequestModel model)
{
var user = await CheckAsync(model.MasterPasswordHash, true);
var reg = await _userService.StartU2fRegistrationAsync(user);
var challenge = new TwoFactorU2fResponseModel.ChallengeModel(user, reg);
return challenge;
2017-06-21 21:46:52 -04:00
}
[HttpPut("u2f")]
[HttpPost("u2f")]
public async Task<TwoFactorU2fResponseModel> PutU2f([FromBody]TwoFactorU2fRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, true);
2018-10-10 15:21:54 -04:00
var success = await _userService.CompleteU2fRegistrationAsync(
user, model.Id.Value, model.Name, model.DeviceResponse);
if(!success)
{
throw new BadRequestException("Unable to complete U2F key registration.");
}
2018-10-08 14:38:11 -04:00
var response = new TwoFactorU2fResponseModel(user);
return response;
}
2018-10-10 15:21:54 -04:00
2018-10-08 14:38:11 -04:00
[HttpDelete("u2f")]
public async Task<TwoFactorU2fResponseModel> DeleteU2f([FromBody]TwoFactorU2fDeleteRequestModel model)
{
var user = await CheckAsync(model.MasterPasswordHash, true);
await _userService.DeleteU2fKeyAsync(user, model.Id.Value);
2017-06-21 21:46:52 -04:00
var response = new TwoFactorU2fResponseModel(user);
return response;
}
[HttpPost("get-email")]
public async Task<TwoFactorEmailResponseModel> GetEmail([FromBody]TwoFactorRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
var response = new TwoFactorEmailResponseModel(user);
return response;
}
2017-06-20 14:12:31 -04:00
2017-06-20 09:21:35 -04:00
[HttpPost("send-email")]
public async Task SendEmail([FromBody]TwoFactorEmailRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
model.ToUser(user);
await _userService.SendTwoFactorEmailAsync(user);
2017-06-20 09:21:35 -04:00
}
2017-06-24 09:20:12 -04:00
[AllowAnonymous]
[HttpPost("send-email-login")]
public async Task SendEmailLogin([FromBody]TwoFactorEmailRequestModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email.ToLowerInvariant());
if(user != null)
{
2018-04-17 08:10:17 -04:00
if(await _userService.CheckPasswordAsync(user, model.MasterPasswordHash))
2017-06-24 09:20:12 -04:00
{
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)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
model.ToUser(user);
2018-12-19 22:27:45 -05:00
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")]
2017-06-21 14:19:07 -04:00
public async Task<TwoFactorProviderResponseModel> PutDisable([FromBody]TwoFactorProviderRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value);
2017-06-21 14:19:07 -04:00
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
return response;
}
2018-10-08 14:38:11 -04:00
2018-04-02 23:18:26 -04:00
[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.MasterPasswordHash, false);
var orgIdGuid = new Guid(id);
if(!_currentContext.OrganizationAdmin(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;
}
2017-06-24 17:16:05 -04:00
[HttpPost("get-recover")]
public async Task<TwoFactorRecoverResponseModel> GetRecover([FromBody]TwoFactorRequestModel model)
{
2017-07-06 16:38:28 -04:00
var user = await CheckAsync(model.MasterPasswordHash, false);
2017-06-24 17:16:05 -04:00
var response = new TwoFactorRecoverResponseModel(user);
return response;
}
[HttpPost("recover")]
[AllowAnonymous]
2017-06-24 17:16:05 -04:00
public async Task PostRecover([FromBody]TwoFactorRecoveryRequestModel model)
{
if(!await _userService.RecoverTwoFactorAsync(model.Email, model.MasterPasswordHash, model.RecoveryCode))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "Invalid information. Try again.");
}
}
2017-07-06 16:38:28 -04:00
private async Task<User> CheckAsync(string masterPasswordHash, bool premium)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if(user == null)
{
throw new UnauthorizedAccessException();
}
2018-04-17 08:10:17 -04:00
if(!await _userService.CheckPasswordAsync(user, masterPasswordHash))
{
await Task.Delay(2000);
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
}
2017-06-20 14:12:31 -04:00
if(premium && !(await _userService.CanAccessPremium(user)))
2017-07-06 16:38:28 -04:00
{
throw new BadRequestException("Premium status is required.");
2017-07-06 16:38:28 -04:00
}
return user;
}
2017-09-11 23:07:27 -04:00
private async Task ValidateYubiKeyAsync(User user, string name, string value)
{
if(string.IsNullOrWhiteSpace(value) || value.Length == 12)
{
return;
}
2018-12-19 22:27:45 -05:00
if(!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
2017-09-11 23:07:27 -04:00
{
await Task.Delay(2000);
throw new BadRequestException(name, $"{name} is invalid.");
}
else
{
await Task.Delay(500);
}
}
}
}