using System; using System.Globalization; using System.IO; using System.Text; using System.Threading.Tasks; using AutoMapper; using gehGassi.Core.Interfaces; using gehGassi.Core.Services; using gehGassi.Domain.Users; using gehGassi.Web.Helper; using gehGassi.Web.Models; using gehGassi.Web.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace gehGassi.Web.Controllers { /// /// Controller für die Verwaltung von Profilen usw. /// [Authorize] public class ManageController : BaseController { private readonly IEmailSender _emailSender; private readonly ILogger _logger; private readonly IStringLocalizer _localizer; private readonly IFileService _fileService; private readonly UserManager _userManager; private readonly IMapper _mapper; private readonly IOptions _licenseOptions; /// /// Erstellt eine Instanz /// /// Instanz eines IEmailSender /// Instanz eines ILogger /// Instanz eines IStringLocalizer /// Instanz eines IFileService /// Instanz eines UserManager /// Instanz eines IMapper /// Instanz von LicenseOptions public ManageController(IEmailSender emailSender, ILogger logger, IStringLocalizer localizer, IFileService fileService, UserManager userManager, IMapper mapper, IOptions licenseOptions) { _emailSender = emailSender; _logger = logger; _localizer = localizer; _fileService = fileService; _userManager = userManager; _mapper = mapper; _licenseOptions = licenseOptions; } /// /// Bearbeiten des Benutzerprofils /// /// View [HttpGet] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public async Task EditProfile() { var userInfo = await UserService.GetByUsernameAsync(ApplicationUser.UserName); if (userInfo == null) { throw new ApplicationException($"Unable to load user '{User.Identity.Name}'."); } var model = new EditProfileViewModel() { FirstName = userInfo.FirstName, LastName = userInfo.LastName, TimeZoneId = userInfo.TimeZoneId, PreferredLanguage = userInfo.PreferredLanguage, Photo = userInfo.Photo, TwoFactorEnabled = userInfo.TwoFactorEnabled }; return View(model); } /// /// Bearbeiten des Benutzerprofils /// /// EditProfileViewModel /// Json [HttpPost] [ValidateAntiForgeryToken] public async Task EditProfile(EditProfileViewModel model) { var result = new ResponseVm { Success = false }; if (ModelState.IsValid) { var userInfo = await UserService.GetByUsernameAsync(ApplicationUser.UserName); if (userInfo == null) { throw new ApplicationException($"Unable to load user '{User.Identity.Name}'."); } var oldPhoto = userInfo.Photo; var languageChanged = userInfo.PreferredLanguage != model.PreferredLanguage; userInfo.FirstName = model.FirstName; userInfo.LastName = model.LastName; userInfo.PreferredLanguage = model.PreferredLanguage; userInfo.TimeZoneId = model.TimeZoneId; userInfo.Photo = model.Photo; Response.Cookies.Append( CookieRequestCultureProvider.DefaultCookieName, CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(model.PreferredLanguage)), new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }); if (languageChanged) CultureInfo.CurrentCulture = new CultureInfo(model.PreferredLanguage); if (oldPhoto != userInfo.Photo) { //Alte Daten löschen, neue Daten anlegen if (!string.IsNullOrWhiteSpace(oldPhoto)) { await _fileService.DeleteAsync(FileServiceHelper.DocumentContainer, oldPhoto); await RemoveThumbnail(FileServiceHelper.DocumentContainer, oldPhoto, 200); await RemoveThumbnail(FileServiceHelper.DocumentContainer, oldPhoto, 100); } if (!string.IsNullOrWhiteSpace(userInfo.Photo)) { //Neue Bilddaten verwenden.... var extension = Path.GetExtension(userInfo.Photo); var fileName = Path.GetFileName(userInfo.Photo); var filenameToUse = FileServiceHelper.GetProfilePath(userInfo.Id) + $"photo-{Guid.NewGuid():N}{extension}"; userInfo.Photo = filenameToUse; //Kopieren var tempFile = await _fileService.GetAsync(FileServiceHelper.TempContainer, model.Photo); await _fileService.StoreAsync(FileServiceHelper.DocumentContainer, filenameToUse, tempFile); //Thumbnails await GenerateThumbnail(FileServiceHelper.DocumentContainer, filenameToUse, 200); await GenerateThumbnail(FileServiceHelper.DocumentContainer, filenameToUse, 100); } } await UserService.CommitAsync(User.Identity.Name); result.Success = true; var userInfoModel = _mapper.Map(userInfo); result.Data = userInfo.ToCamelCaseJson(); result.Html = string.Empty; return Json(new { result.Success, result.Html, result.Data, languageChanged, returnUrl = Url.Action("EditProfile") }); } result.Html = await PartialView("EditProfile", model).ToStringAsync(ControllerContext); return Json(result); } /// /// Gibt eine View für das Ändern des Benutzer-Passwortes zurück /// /// View [HttpGet] public async Task ChangePassword() { var userInfo = await UserService.GetByUsernameAsync(ApplicationUser.UserName); if (userInfo == null) { throw new ApplicationException($"Unable to load user '{User.Identity.Name}'."); } var hasPassword = await _userManager.HasPasswordAsync(userInfo); if (!hasPassword) { return RedirectToAction(nameof(SetPassword)); } var model = new ChangePasswordViewModel(); return View(model); } /// /// Ändern des Benutzer Passwortes /// /// ChangePasswordViewModel /// Json [HttpPost] [ValidateAntiForgeryToken] public async Task ChangePassword(ChangePasswordViewModel model) { var result = new ResponseVm(); result.Success = false; if (ModelState.IsValid) { var userInfo = await _userManager.FindByIdAsync(ApplicationUser.Id); if (userInfo == null) { throw new ApplicationException($"Unable to load user '{User.Identity.Name}'."); } var changeResult = await _userManager.ChangePasswordAsync(userInfo, model.OldPassword, model.NewPassword); if (changeResult.Succeeded) { result.Success = true; result.Html = string.Empty; return Json(result); } else { foreach (var identityError in changeResult.Errors) { var key = string.Empty; if (identityError.Code.ToLower().Contains("passwordmismatch")) key = "OldPassword"; else if (identityError.Code.ToLower().Contains("password")) key = "NewPassword"; else if (identityError.Code.ToLower().Contains("user")) key = "Username"; else if (identityError.Code.ToLower().Contains("email")) key = "Username"; ModelState.AddModelError(key, identityError.Description); } } } result.Html = await PartialView("ChangePassword", model).ToStringAsync(ControllerContext); return Json(result); } /// /// Setzen eines Passwortes /// /// View [HttpGet] public async Task SetPassword() { var userInfo = await UserService.GetByUsernameAsync(ApplicationUser.UserName); if (userInfo == null) { throw new ApplicationException($"Unable to load user '{User.Identity.Name}'."); } var hasPassword = await _userManager.HasPasswordAsync(userInfo); if (!hasPassword) { return RedirectToAction(nameof(SetPassword)); } var model = new SetPasswordViewModel(); return View(model); } /// /// Setzen eines Passwortes /// /// SetPasswordViewModel /// Json [HttpPost] [ValidateAntiForgeryToken] public async Task SetPassword(SetPasswordViewModel model) { var result = new ResponseVm(); result.Success = false; if (ModelState.IsValid) { var userInfo = await _userManager.FindByIdAsync(ApplicationUser.Id); if (userInfo == null) { throw new ApplicationException($"Unable to load user '{User.Identity.Name}'."); } var setResult = await _userManager.AddPasswordAsync(userInfo, model.NewPassword); if (setResult.Succeeded) { result.Success = true; result.Html = string.Empty; return Json(result); } else { foreach (var identityError in setResult.Errors) { var key = string.Empty; if (identityError.Code.ToLower().Contains("password")) key = "Password"; else if (identityError.Code.ToLower().Contains("user")) key = "Username"; else if (identityError.Code.ToLower().Contains("email")) key = "Username"; ModelState.AddModelError(key, identityError.Description); } } } result.Html = await PartialView("SetPassword", model).ToStringAsync(ControllerContext); return Json(result); } #region 2 Faktor /// /// Aktivieren der 2 Faktor Authentifizierung /// /// PartialView [HttpGet] public async Task TwoFactorEnable() { var user = await _userManager.FindByEmailAsync(User.Identity.Name); if (user != null) { var keyTuple = await LoadSharedKeyAndQrCodeUriAsync(user); var model = new EnableTwoFactorVm() { Code = string.Empty, SharedKey = keyTuple.Item1, AuthenticatorUri = keyTuple.Item2 }; return PartialView($"_TwoFactorEnable_{CultureInfo.CurrentCulture.TwoLetterISOLanguageName}", model); } return PartialView("_Error"); } /// /// Aktivieren der 2 Faktor Authentifizierung /// /// JSON [HttpPost] public async Task TwoFactorEnable(EnableTwoFactorVm model) { var result = new ResponseVm { Success = false }; var user = await _userManager.FindByEmailAsync(User.Identity.Name); if (ModelState.IsValid) { // Strip spaces and hypens var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty); var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); if (is2faTokenValid) { await _userManager.SetTwoFactorEnabledAsync(user, true); result.Success = true; return Json(result); } else { ModelState.AddModelError("Code", _localizer["Err_Invalid_Code"]); } } var keyTuple = await LoadSharedKeyAndQrCodeUriAsync(user); model.SharedKey = keyTuple.Item1; model.AuthenticatorUri = keyTuple.Item2; result.Html = await PartialView($"_TwoFactorEnable_{CultureInfo.CurrentCulture.TwoLetterISOLanguageName}", model).ToStringAsync(ControllerContext); return Json(result); } /// /// Deaktivieren der 2 Faktor Authentifizierung /// /// JSON [HttpGet] public async Task TwoFactorDisable() { var result = new ResponseVm { Success = false }; var user = await _userManager.FindByEmailAsync(User.Identity.Name); if (user != null) { var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); if (disable2faResult.Succeeded) { result.Success = true; } } return Json(result); } #region 2-Faktor Helper private async Task> LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) { // Load the authenticator key & QR code URI to display on the form var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); if (string.IsNullOrEmpty(unformattedKey)) { await _userManager.ResetAuthenticatorKeyAsync(user); unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); } var sharedKey = FormatKey(unformattedKey); var email = await _userManager.GetEmailAsync(user); var authenticatorUri = GenerateQrCodeUri(email, unformattedKey); return new Tuple(sharedKey, authenticatorUri); } private string FormatKey(string unformattedKey) { var result = new StringBuilder(); int currentPosition = 0; while (currentPosition + 4 < unformattedKey.Length) { result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" "); currentPosition += 4; } if (currentPosition < unformattedKey.Length) { result.Append(unformattedKey.Substring(currentPosition)); } return result.ToString().ToLowerInvariant(); } private string GenerateQrCodeUri(string email, string unformattedKey) { string authenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; return string.Format(authenticatorUriFormat, System.Net.WebUtility.UrlEncode(_licenseOptions.Value.Software), System.Net.WebUtility.UrlEncode(email), unformattedKey); } #endregion #endregion #region Upload /// /// Hochladen eines Files. Speichert ins temporäre Verzeichnis /// /// Bild-Datei /// Json true wenn erfolgreich, false sons [HttpPost] public async Task UploadTempFile(IFormFile file) { if (file.Length > 0) { try { var userInfo = await UserService.GetByUsernameAsync(ApplicationUser.UserName); var path = Path.GetFileName(file.FileName); var extension = Path.GetExtension(path); var fileName = FileServiceHelper.GetProfilePath(userInfo.Id) + Path.GetFileName($"{Guid.NewGuid().ToString()}{extension}"); fileName = FileServiceHelper.SanitizeFileName(fileName); await _fileService.StoreAsync(FileServiceHelper.TempContainer, fileName, file.OpenReadStream()); return Json(new { filename = fileName }); } catch { return Json(new { filename = "" }); } } return Json(new { filename = "" }); } /// /// Löschem eines Files im temporäre Verzeichnis /// /// Dateinamr Bild-Datei /// Json true wenn erfolgreich, false sons [HttpPost] public async Task RemoveTempFile(string fileName) { if (!string.IsNullOrWhiteSpace(fileName)) { try { await _fileService.DeleteAsync(FileServiceHelper.TempContainer, fileName); return Json(true); } catch { return Json(false); } } return Json(false); } #endregion } }