486 lines
19 KiB
C#
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
|
|
}
|
|
} |