<commit_before>﻿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;
using Bit.Core;
using Newtonsoft.Json;

namespace Bit.Api.Controllers
{
    [Route("two-factor")]
    [Authorize("Web")]
    public class TwoFactorController : Controller
    {
        private readonly IUserService _userService;
        private readonly GlobalSettings _globalSettings;
        private readonly UserManager<User> _userManager;

        public TwoFactorController(
            IUserService userService,
            GlobalSettings globalSettings,
            UserManager<User> userManager)
        {
            _userService = userService;
            _globalSettings = globalSettings;
            _userManager = userManager;
        }

        [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);
        }

        [HttpPost("get-authenticator")]
        public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator([FromBody]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var response = new TwoFactorAuthenticatorResponseModel(user);
            return response;
        }

        [HttpPut("authenticator")]
        [HttpPost("authenticator")]
        public async Task<TwoFactorAuthenticatorResponseModel> PutAuthenticator(
            [FromBody]UpdateTwoFactorAuthenticatorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            model.ToUser(user);

            if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.Authenticator.ToString(), 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]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var response = new TwoFactorYubiKeyResponseModel(user);
            return response;
        }

        [HttpPut("yubikey")]
        [HttpPost("yubikey")]
        public async Task<TwoFactorYubiKeyResponseModel> PutYubiKey([FromBody]UpdateTwoFactorYubicoOtpRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            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]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var response = new TwoFactorDuoResponseModel(user);
            return response;
        }

        [HttpPut("duo")]
        [HttpPost("duo")]
        public async Task<TwoFactorDuoResponseModel> PutDuo([FromBody]UpdateTwoFactorDuoRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            model.ToUser(user);
            await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo);
            var response = new TwoFactorDuoResponseModel(user);
            return response;
        }

        [HttpPost("get-u2f")]
        public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
            if(provider == null || !provider.Enabled || (provider.MetaData?.Count ?? 0) > 0)
            {
                var reg = await _userService.StartU2fRegistrationAsync(user);
                var response = new TwoFactorU2fResponseModel(user, provider, reg);
                return response;
            }
            else
            {
                var response = new TwoFactorU2fResponseModel(user, provider);
                return response;
            }
        }

        [HttpGet("~/app-id.json")]
        [Produces("application/fido.trusted-apps+json")]
        [AllowAnonymous]
        public string GetU2fAppId()
        {
            return JsonConvert.SerializeObject(new
            {
                trustedFacets = new object[]
                {
                    new
                    {
                        version = new
                        {
                            major = 1,
                            minor = 0
                        },
                        ids = new string[]
                        {
                            _globalSettings.U2f.AppId,
                            //"ios:bundle-id:com.8bit.bitwarden",
                            //"android:apk-key-hash:585215fd5153209a7e246f53286035838a0be227",
                            //"chrome-extension://nngceckbapebfimnlniiiahkandclblb"
                        }
                    }
                }
            });
        }

        [HttpPut("u2f")]
        [HttpPost("u2f")]
        public async Task<TwoFactorU2fResponseModel> PutU2f([FromBody]TwoFactorU2fRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            await _userService.CompleteU2fRegistrationAsync(user, model.DeviceResponse);
            var response = new TwoFactorU2fResponseModel(user);
            return response;
        }

        public async Task ValidateYubiKeyAsync(User user, string name, string value)
        {
            if(string.IsNullOrWhiteSpace(value) || value.Length == 12)
            {<commit_msg>no need for chrome ext to have appid<commit_after>﻿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;
using Bit.Core;
using Newtonsoft.Json;

namespace Bit.Api.Controllers
{
    [Route("two-factor")]
    [Authorize("Web")]
    public class TwoFactorController : Controller
    {
        private readonly IUserService _userService;
        private readonly GlobalSettings _globalSettings;
        private readonly UserManager<User> _userManager;

        public TwoFactorController(
            IUserService userService,
            GlobalSettings globalSettings,
            UserManager<User> userManager)
        {
            _userService = userService;
            _globalSettings = globalSettings;
            _userManager = userManager;
        }

        [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);
        }

        [HttpPost("get-authenticator")]
        public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator([FromBody]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var response = new TwoFactorAuthenticatorResponseModel(user);
            return response;
        }

        [HttpPut("authenticator")]
        [HttpPost("authenticator")]
        public async Task<TwoFactorAuthenticatorResponseModel> PutAuthenticator(
            [FromBody]UpdateTwoFactorAuthenticatorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            model.ToUser(user);

            if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.Authenticator.ToString(), 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]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var response = new TwoFactorYubiKeyResponseModel(user);
            return response;
        }

        [HttpPut("yubikey")]
        [HttpPost("yubikey")]
        public async Task<TwoFactorYubiKeyResponseModel> PutYubiKey([FromBody]UpdateTwoFactorYubicoOtpRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            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]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var response = new TwoFactorDuoResponseModel(user);
            return response;
        }

        [HttpPut("duo")]
        [HttpPost("duo")]
        public async Task<TwoFactorDuoResponseModel> PutDuo([FromBody]UpdateTwoFactorDuoRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            model.ToUser(user);
            await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo);
            var response = new TwoFactorDuoResponseModel(user);
            return response;
        }

        [HttpPost("get-u2f")]
        public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
            if(provider == null || !provider.Enabled || (provider.MetaData?.Count ?? 0) > 0)
            {
                var reg = await _userService.StartU2fRegistrationAsync(user);
                var response = new TwoFactorU2fResponseModel(user, provider, reg);
                return response;
            }
            else
            {
                var response = new TwoFactorU2fResponseModel(user, provider);
                return response;
            }
        }

        [HttpGet("~/app-id.json")]
        [Produces("application/fido.trusted-apps+json")]
        [AllowAnonymous]
        public string GetU2fAppId()
        {
            return JsonConvert.SerializeObject(new
            {
                trustedFacets = new object[]
                {
                    new
                    {
                        version = new
                        {
                            major = 1,
                            minor = 0
                        },
                        ids = new string[]
                        {
                            _globalSettings.U2f.AppId,
                            //"ios:bundle-id:com.8bit.bitwarden",
                            //"android:apk-key-hash:585215fd5153209a7e246f53286035838a0be227"
                        }
                    }
                }
            });
        }

        [HttpPut("u2f")]
        [HttpPost("u2f")]
        public async Task<TwoFactorU2fResponseModel> PutU2f([FromBody]TwoFactorU2fRequestModel model)
        {
            var user = await CheckPasswordAsync(model.MasterPasswordHash);
            await _userService.CompleteU2fRegistrationAsync(user, model.DeviceResponse);
            var response = new TwoFactorU2fResponseModel(user);
            return response;
        }

        public async Task ValidateYubiKeyAsync(User user, string name, string value)
        {
            if(string.IsNullOrWhiteSpace(value) || value.Length == 12)
            {
{'fraction_non_alphanumeric': 0.08348728151200796, 'fraction_numerical': 0.009805314764814551, 'mean_word_length': 2.1277777777777778, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7138997', 'n_tokens_mistral': 3660, 'n_tokens_neox': 3583, 'n_words': 650}