using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Web.BackOffice.Controllers { public class TwoFactorLoginController : UmbracoAuthorizedJsonController { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly ILogger _logger; private readonly ITwoFactorLoginService2 _twoFactorLoginService; private readonly IBackOfficeSignInManager _backOfficeSignInManager; private readonly IBackOfficeUserManager _backOfficeUserManager; private readonly IOptionsSnapshot _twoFactorLoginViewOptions; public TwoFactorLoginController( IBackOfficeSecurityAccessor backOfficeSecurityAccessor, ILogger logger, ITwoFactorLoginService twoFactorLoginService, IBackOfficeSignInManager backOfficeSignInManager, IBackOfficeUserManager backOfficeUserManager, IOptionsSnapshot twoFactorLoginViewOptions) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _logger = logger; if (twoFactorLoginService is not ITwoFactorLoginService2 twoFactorLoginService2) { throw new ArgumentException("twoFactorLoginService needs to implement ITwoFactorLoginService2 until the interfaces are merged", nameof(twoFactorLoginService)); } _twoFactorLoginService = twoFactorLoginService2; _backOfficeSignInManager = backOfficeSignInManager; _backOfficeUserManager = backOfficeUserManager; _twoFactorLoginViewOptions = twoFactorLoginViewOptions; } /// /// Used to retrieve the 2FA providers for code submission /// /// [HttpGet] [AllowAnonymous] public async Task>> GetEnabled2FAProvidersForCurrentUser() { var user = await _backOfficeSignInManager.GetTwoFactorAuthenticationUserAsync(); if (user == null) { _logger.LogWarning("No verified user found, returning 404"); return NotFound(); } var userFactors = await _backOfficeUserManager.GetValidTwoFactorProvidersAsync(user); return new ObjectResult(userFactors); } [HttpGet] public async Task>> Get2FAProvidersForUser(int userId) { var user = await _backOfficeUserManager.FindByIdAsync(userId.ToString()); var enabledProviderNameHashSet = new HashSet(await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key)); var providerNames = await _backOfficeUserManager.GetValidTwoFactorProvidersAsync(user); return providerNames.Select(providerName => new UserTwoFactorProviderModel(providerName, enabledProviderNameHashSet.Contains(providerName))).ToArray(); } [HttpGet] public async Task> SetupInfo(string providerName) { var user = _backOfficeSecurityAccessor?.BackOfficeSecurity.CurrentUser; var setupInfo = await _twoFactorLoginService.GetSetupInfoAsync(user.Key, providerName); return setupInfo; } [HttpPost] public async Task> ValidateAndSave(string providerName, string secret, string code) { var user = _backOfficeSecurityAccessor?.BackOfficeSecurity.CurrentUser; return await _twoFactorLoginService.ValidateAndSaveAsync(providerName, user.Key, secret, code); } [HttpPost] [Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)] public async Task> Disable(string providerName, Guid userKey) { return await _twoFactorLoginService.DisableAsync(userKey, providerName); } [HttpPost] public async Task> DisableWithCode(string providerName, string code) { Guid key = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Key; return await _twoFactorLoginService.DisableWithCodeAsync(providerName, key, code); } [HttpGet] public ActionResult ViewPathForProviderName(string providerName) { var options = _twoFactorLoginViewOptions.Get(providerName); return options.SetupViewPath; } } }