<commit_before>﻿using Bit.Api.Models.Response;
using Bit.Core.Domains;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using IdentityServer4.Models;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Bit.Api.IdentityServer
{
    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {
        private readonly UserManager<User> _userManager;
        private readonly IdentityOptions _identityOptions;
        private readonly IDeviceRepository _deviceRepository;

        public ResourceOwnerPasswordValidator(
            UserManager<User> userManager,
            IOptions<IdentityOptions> optionsAccessor,
            IDeviceRepository deviceRepository)
        {
            _userManager = userManager;
            _identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
            _deviceRepository = deviceRepository;
        }

        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            var twoFactorCode = context.Request.Raw["twoFactorCode"]?.ToString();
            var twoFactorProvider = context.Request.Raw["twoFactorProvider"]?.ToString();
            var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorCode) &&
                !string.IsNullOrWhiteSpace(twoFactorProvider);

            var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
            if(user != null)
            {
                if(await _userManager.CheckPasswordAsync(user, context.Password))
                {
                    if(!twoFactorRequest && await TwoFactorRequiredAsync(user))
                    {
                        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Two factor code required.",
                            new System.Collections.Generic.Dictionary<string, object> { { "TwoFactorRequired", true } });
                        return;
                    }

                    if(!twoFactorRequest || await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode))
                    {
                        await SaveDeviceAsync(user, context);

                        context.Result = new GrantValidationResult(user.Id.ToString(), "Application", identityProvider: "bitwarden",
                            claims: new Claim[] {
                                // Deprecated claims for backwards compatability
                                new Claim(ClaimTypes.AuthenticationMethod, "Application"),
                                new Claim(_identityOptions.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
                                new Claim(_identityOptions.ClaimsIdentity.UserNameClaimType, user.Email.ToString()),
                                new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp)
                            });
                        return;
                    }
                }
            }

            await Task.Delay(2000);
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse:
               new System.Collections.Generic.Dictionary<string, object> { {
                       "ErrorModel", new ErrorResponseModel(twoFactorRequest ?
                       "Code is not correct. Try again." : "Username or password is incorrect. Try again.")
                   } });
        }

        private async Task<bool> TwoFactorRequiredAsync(User user)
        {
            return _userManager.SupportsUserTwoFactor &&
                await _userManager.GetTwoFactorEnabledAsync(user) &&
                (await _userManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
        }

        private Device GetDeviceFromRequest(ResourceOwnerPasswordValidationContext context)
        {
            var deviceIdentifier = context.Request.Raw["deviceIdentifier"]?.ToString();
            var deviceType = context.Request.Raw["deviceType"]?.ToString();
            var deviceName = context.Request.Raw["deviceName"]?.ToString();
            var devicePushToken = context.Request.Raw["devicePushToken"]?.ToString();

            DeviceType type;
            if(string.IsNullOrWhiteSpace(deviceIdentifier) || string.IsNullOrWhiteSpace(deviceType) ||
                string.IsNullOrWhiteSpace(deviceName) || !Enum.TryParse(deviceType, out type))<commit_msg>validate old auth bearer tokens so that we can generate new identity ones<commit_after>﻿using Bit.Api.Models.Response;
using Bit.Core.Domains;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.Repositories;
using IdentityServer4.Models;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Bit.Api.IdentityServer
{
    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {
        private UserManager<User> _userManager;
        private IdentityOptions _identityOptions;
        private JwtBearerOptions _jwtBearerOptions;
        private JwtBearerIdentityOptions _jwtBearerIdentityOptions;
        private readonly IDeviceRepository _deviceRepository;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public ResourceOwnerPasswordValidator(
            IDeviceRepository deviceRepository,
            IHttpContextAccessor httpContextAccessor)
        {
            _deviceRepository = deviceRepository;
            _httpContextAccessor = httpContextAccessor;
        }

        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            Init();

            var oldAuthBearer = context.Request.Raw["oldAuthBearer"]?.ToString();
            var twoFactorCode = context.Request.Raw["twoFactorCode"]?.ToString();
            var twoFactorProvider = context.Request.Raw["twoFactorProvider"]?.ToString();
            var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorCode) &&
                !string.IsNullOrWhiteSpace(twoFactorProvider);

            if(!string.IsNullOrWhiteSpace(oldAuthBearer) && _jwtBearerOptions != null)
            {
                // support transferring the old auth bearer token
                var ticket = ValidateOldAuthBearer(oldAuthBearer);
                if(ticket != null && ticket.Principal != null)
                {
                    var idClaim = ticket.Principal.Claims.FirstOrDefault(c => c.Type == _identityOptions.ClaimsIdentity.UserIdClaimType);
                    var securityTokenClaim = ticket.Principal.Claims.FirstOrDefault(c => c.Type == _identityOptions.ClaimsIdentity.SecurityStampClaimType);
                    if(idClaim != null && securityTokenClaim != null)
                    {
                        var user = await _userManager.FindByIdAsync(idClaim.Value);
                        if(user != null && user.SecurityStamp == securityTokenClaim.Value)
                        {
                            BuildSuccessResult(user, context);
                            return;
                        }
                    }
                }
            }
            else if(!string.IsNullOrWhiteSpace(context.UserName))
            {
                var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
                if(user != null)
                {
                    if(await _userManager.CheckPasswordAsync(user, context.Password))
                    {
                        if(!twoFactorRequest && await TwoFactorRequiredAsync(user))
                        {
                            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Two factor code required.",
                                new Dictionary<string, object> { { "TwoFactorRequired", true } });
                            return;
                        }

                        if(!twoFactorRequest || await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode))
                        {
                            await SaveDeviceAsync(user, context);
                            BuildSuccessResult(user, context);
                            return;
                        }
                    }
                }
            }

            await Task.Delay(2000);
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse:
               new Dictionary<string, object> { {
                       "ErrorModel", new ErrorResponseModel(twoFactorRequest ?
                       "Code is not correct. Try again." : "Username or password is incorrect. Try again.")
                   } });
        }

        private void Init()
        {
            var httpContext = _httpContextAccessor.HttpContext;
            _userManager = httpContext.RequestServices.GetRequiredService<UserManager<User>>();
            _identityOptions = httpContext.RequestServices.GetRequiredService<IOptions<IdentityOptions>>()?.Value ?? new IdentityOptions();
            _jwtBearerOptions = httpContext.RequestServices.GetRequiredService<IOptions<JwtBearerOptions>>()?.Value;
            _jwtBearerIdentityOptions = httpContext.RequestServices.GetRequiredService<IOptions<JwtBearerIdentityOptions>>()?.Value;
        }

        private void BuildSuccessResult(User user, ResourceOwnerPasswordValidationContext context)
        {
            context.Result = new GrantValidationResult(user.Id.ToString(), "Application", identityProvider: "bitwarden",
                claims: new Claim[] {
                    // Deprecated claims for backwards compatability
                    new Claim(ClaimTypes.AuthenticationMethod, "Application"),
                    new Claim(_identityOptions.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
                    new Claim(_identityOptions.ClaimsIdentity.UserNameClaimType, user.Email.ToString()),
                    new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp)
            });
        }

        private AuthenticationTicket ValidateOldAuthBearer(string token)
        {
            SecurityToken validatedToken;
            foreach(var validator in _jwtBearerOptions.SecurityTokenValidators)
            {
                if(validator.CanReadToken(token))
                {
                    ClaimsPrincipal principal;
                    try
                    {
                        principal = validator.ValidateToken(token,
                            _jwtBearerOptions.TokenValidationParameters, out validatedToken);
                    }
                    catch
                    {
                        continue;
                    }

                    var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
                        _jwtBearerOptions.AuthenticationScheme);

                    return ticket;
                }
            }

            return null;
        }

        private async Task<bool> TwoFactorRequiredAsync(User user)
        {
            return _userManager.SupportsUserTwoFactor &&
                await _userManager.GetTwoFactorEnabledAsync(user) &&
                (await _userManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
        }

        private Device GetDeviceFromRequest(ResourceOwnerPasswordValidationContext context)
        {
            var deviceIdentifier = context.Request.Raw["deviceIdentifier"]?.ToString();
            var deviceType = context.Request.Raw["deviceType"]?.ToString();
            var deviceName = context.Request.Raw["deviceName"]?.ToString();
            var devicePushToken = context.Request.Raw["devicePushToken"]?.ToString();

            DeviceType type;
            if(string.IsNullOrWhiteSpace(deviceIdentifier) || string.IsNullOrWhiteSpace(deviceType) ||
                string.IsNullOrWhiteSpace(deviceName) || !Enum.TryParse(deviceType, out type))
{'fraction_non_alphanumeric': 0.07863727454909819, 'fraction_numerical': 0.0011222444889779559, 'mean_word_length': 2.1073474470734745, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2272298', 'n_tokens_mistral': 2848, 'n_tokens_neox': 2744, 'n_words': 523}