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
}
}