486 lines
19 KiB
C#

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
{
/// <summary>
/// Controller für die Verwaltung von Profilen usw.
/// </summary>
[Authorize]
public class ManageController : BaseController
{
private readonly IEmailSender _emailSender;
private readonly ILogger<ManageController> _logger;
private readonly IStringLocalizer<ManageController> _localizer;
private readonly IFileService _fileService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IMapper _mapper;
private readonly IOptions<LicenseOptions> _licenseOptions;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="emailSender">Instanz eines IEmailSender</param>
/// <param name="logger">Instanz eines ILogger</param>
/// <param name="localizer">Instanz eines IStringLocalizer</param>
/// <param name="fileService">Instanz eines IFileService</param>
/// <param name="userManager">Instanz eines UserManager</param>
/// <param name="mapper">Instanz eines IMapper</param>
/// <param name="licenseOptions">Instanz von LicenseOptions</param>
public ManageController(IEmailSender emailSender, ILogger<ManageController> logger, IStringLocalizer<ManageController> localizer, IFileService fileService,
UserManager<ApplicationUser> userManager, IMapper mapper, IOptions<LicenseOptions> licenseOptions)
{
_emailSender = emailSender;
_logger = logger;
_localizer = localizer;
_fileService = fileService;
_userManager = userManager;
_mapper = mapper;
_licenseOptions = licenseOptions;
}
/// <summary>
/// Bearbeiten des Benutzerprofils
/// </summary>
/// <returns>View</returns>
[HttpGet]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Bearbeiten des Benutzerprofils
/// </summary>
/// <param name="model">EditProfileViewModel</param>
/// <returns>Json</returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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<ApplicationUserInfoVm>(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);
}
/// <summary>
/// Gibt eine View für das Ändern des Benutzer-Passwortes zurück
/// </summary>
/// <returns>View</returns>
[HttpGet]
public async Task<IActionResult> 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);
}
/// <summary>
/// Ändern des Benutzer Passwortes
/// </summary>
/// <param name="model">ChangePasswordViewModel</param>
/// <returns>Json</returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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);
}
/// <summary>
/// Setzen eines Passwortes
/// </summary>
/// <returns>View</returns>
[HttpGet]
public async Task<IActionResult> 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);
}
/// <summary>
/// Setzen eines Passwortes
/// </summary>
/// <param name="model">SetPasswordViewModel</param>
/// <returns>Json</returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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
/// <summary>
/// Aktivieren der 2 Faktor Authentifizierung
/// </summary>
/// <returns>PartialView</returns>
[HttpGet]
public async Task<IActionResult> 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");
}
/// <summary>
/// Aktivieren der 2 Faktor Authentifizierung
/// </summary>
/// <returns>JSON</returns>
[HttpPost]
public async Task<IActionResult> 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);
}
/// <summary>
/// Deaktivieren der 2 Faktor Authentifizierung
/// </summary>
/// <returns>JSON</returns>
[HttpGet]
public async Task<IActionResult> 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<Tuple<string, string>> 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<string, string>(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
/// <summary>
/// Hochladen eines Files. Speichert ins temporäre Verzeichnis
/// </summary>
/// <param name="file">Bild-Datei</param>
/// <returns>Json true wenn erfolgreich, false sons</returns>
[HttpPost]
public async Task<IActionResult> 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 = "" });
}
/// <summary>
/// Löschem eines Files im temporäre Verzeichnis
/// </summary>
/// <param name="fileName">Dateinamr Bild-Datei</param>
/// <returns>Json true wenn erfolgreich, false sons</returns>
[HttpPost]
public async Task<IActionResult> 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
}
}