267 lines
9.5 KiB
C#
267 lines
9.5 KiB
C#
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Security.Claims;
|
|
using watcher_monitoring.Configuration;
|
|
using watcher_monitoring.Data;
|
|
using watcher_monitoring.Models;
|
|
|
|
namespace watcher_monitoring.Controllers;
|
|
|
|
public class AuthController : Controller
|
|
{
|
|
private readonly WatcherDbContext _context;
|
|
private readonly ILogger<AuthController> _logger;
|
|
private readonly OidcSettings _oidcSettings;
|
|
|
|
public AuthController(WatcherDbContext context, ILogger<AuthController> logger, OidcSettings oidcSettings)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
_oidcSettings = oidcSettings;
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpGet]
|
|
public IActionResult Login(string? returnUrl = null)
|
|
{
|
|
// Wenn der Benutzer bereits angemeldet ist, zur Startseite weiterleiten
|
|
if (User.Identity?.IsAuthenticated == true)
|
|
{
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
|
|
ViewData["ReturnUrl"] = returnUrl;
|
|
ViewData["OidcEnabled"] = _oidcSettings.IsValid;
|
|
return View();
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpGet]
|
|
public IActionResult OidcLogin(string? returnUrl = null)
|
|
{
|
|
if (!_oidcSettings.IsValid)
|
|
{
|
|
_logger.LogWarning("OIDC-Login versucht, aber OIDC ist nicht konfiguriert");
|
|
return RedirectToAction("Login");
|
|
}
|
|
|
|
var properties = new AuthenticationProperties
|
|
{
|
|
RedirectUri = Url.Action("OidcCallback", "Auth", new { returnUrl }),
|
|
Items = { { "returnUrl", returnUrl ?? "/" } }
|
|
};
|
|
|
|
return Challenge(properties, OpenIdConnectDefaults.AuthenticationScheme);
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpGet]
|
|
public async Task<IActionResult> OidcCallback(string? returnUrl = null)
|
|
{
|
|
var authenticateResult = await HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);
|
|
|
|
if (!authenticateResult.Succeeded)
|
|
{
|
|
_logger.LogWarning("OIDC-Authentifizierung fehlgeschlagen: {Failure}", authenticateResult.Failure?.Message);
|
|
TempData["Error"] = "OIDC-Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut.";
|
|
return RedirectToAction("Login");
|
|
}
|
|
|
|
var oidcClaims = authenticateResult.Principal?.Claims;
|
|
var oidcSubject = oidcClaims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value
|
|
?? oidcClaims?.FirstOrDefault(c => c.Type == "sub")?.Value;
|
|
|
|
if (string.IsNullOrEmpty(oidcSubject))
|
|
{
|
|
_logger.LogError("OIDC-Claims enthalten keine Subject-ID");
|
|
TempData["Error"] = "OIDC-Anmeldung fehlgeschlagen: Keine Benutzer-ID erhalten.";
|
|
return RedirectToAction("Login");
|
|
}
|
|
|
|
var username = oidcClaims?.FirstOrDefault(c => c.Type == _oidcSettings.ClaimUsername)?.Value
|
|
?? oidcClaims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value
|
|
?? oidcClaims?.FirstOrDefault(c => c.Type == "name")?.Value
|
|
?? oidcSubject;
|
|
|
|
var email = oidcClaims?.FirstOrDefault(c => c.Type == _oidcSettings.ClaimEmail)?.Value
|
|
?? oidcClaims?.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value
|
|
?? $"{username}@oidc.local";
|
|
|
|
var user = await _context.Users.FirstOrDefaultAsync(u => u.OidcSubject == oidcSubject);
|
|
|
|
if (user == null)
|
|
{
|
|
if (!_oidcSettings.AutoProvisionUsers)
|
|
{
|
|
_logger.LogWarning("OIDC-User {Subject} existiert nicht und Auto-Provisioning ist deaktiviert", oidcSubject);
|
|
TempData["Error"] = "Ihr Benutzerkonto existiert nicht. Bitte kontaktieren Sie den Administrator.";
|
|
return RedirectToAction("Login");
|
|
}
|
|
|
|
var existingUsername = await _context.Users.AnyAsync(u => u.Username == username);
|
|
if (existingUsername)
|
|
{
|
|
username = $"{username}_{oidcSubject[..Math.Min(8, oidcSubject.Length)]}";
|
|
}
|
|
|
|
user = new User
|
|
{
|
|
Username = username,
|
|
Email = email,
|
|
OidcSubject = oidcSubject,
|
|
Password = string.Empty,
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
LastLogin = DateTime.UtcNow
|
|
};
|
|
|
|
_context.Users.Add(user);
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInformation("OIDC-User {Username} wurde automatisch erstellt (Subject: {Subject})", username, oidcSubject);
|
|
}
|
|
else
|
|
{
|
|
if (!user.IsActive)
|
|
{
|
|
_logger.LogWarning("OIDC-User {Username} ist deaktiviert", user.Username);
|
|
TempData["Error"] = "Ihr Benutzerkonto ist deaktiviert.";
|
|
return RedirectToAction("Login");
|
|
}
|
|
|
|
user.LastLogin = DateTime.UtcNow;
|
|
user.Email = email;
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
|
|
var claims = new List<Claim>
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new Claim(ClaimTypes.Name, user.Username),
|
|
new Claim(ClaimTypes.Email, user.Email),
|
|
new Claim("LastLogin", user.LastLogin.ToString("o")),
|
|
new Claim("AuthMethod", "OIDC")
|
|
};
|
|
|
|
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
|
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
|
|
|
|
var authProperties = new AuthenticationProperties
|
|
{
|
|
IsPersistent = true,
|
|
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8)
|
|
};
|
|
|
|
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
|
|
|
|
_logger.LogInformation("OIDC-User {Username} erfolgreich angemeldet", user.Username);
|
|
|
|
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
|
|
{
|
|
return Redirect(returnUrl);
|
|
}
|
|
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Login(LoginViewModel model, string? returnUrl = null)
|
|
{
|
|
ViewData["ReturnUrl"] = returnUrl;
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Benutzer suchen
|
|
var user = await _context.Users
|
|
.FirstOrDefaultAsync(u => u.Username == model.Username && u.IsActive);
|
|
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning("Login-Versuch mit ungültigem Benutzernamen: {Username}", model.Username);
|
|
TempData["Error"] = "Ungültiger Benutzername oder Passwort";
|
|
return View(model);
|
|
}
|
|
|
|
// Passwort überprüfen (BCrypt)
|
|
if (!BCrypt.Net.BCrypt.Verify(model.Password, user.Password))
|
|
{
|
|
_logger.LogWarning("Login-Versuch mit falschem Passwort für Benutzer: {Username}", model.Username);
|
|
TempData["Error"] = "Ungültiger Benutzername oder Passwort";
|
|
return View(model);
|
|
}
|
|
|
|
// LastLogin aktualisieren
|
|
user.LastLogin = DateTime.UtcNow;
|
|
await _context.SaveChangesAsync();
|
|
|
|
// Claims erstellen
|
|
var claims = new List<Claim>
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new Claim(ClaimTypes.Name, user.Username),
|
|
new Claim(ClaimTypes.Email, user.Email),
|
|
new Claim("LastLogin", user.LastLogin.ToString("o"))
|
|
};
|
|
|
|
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
|
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
|
|
|
|
var authProperties = new AuthenticationProperties
|
|
{
|
|
IsPersistent = model.RememberMe,
|
|
ExpiresUtc = model.RememberMe ? DateTimeOffset.UtcNow.AddDays(30) : DateTimeOffset.UtcNow.AddHours(8)
|
|
};
|
|
|
|
await HttpContext.SignInAsync(
|
|
CookieAuthenticationDefaults.AuthenticationScheme,
|
|
claimsPrincipal,
|
|
authProperties);
|
|
|
|
_logger.LogInformation("Benutzer {Username} erfolgreich angemeldet", user.Username);
|
|
|
|
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
|
|
{
|
|
return Redirect(returnUrl);
|
|
}
|
|
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Fehler beim Login-Vorgang");
|
|
TempData["Error"] = "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.";
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
var username = User.Identity?.Name ?? "Unbekannt";
|
|
|
|
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
|
|
|
_logger.LogInformation("Benutzer {Username} erfolgreich abgemeldet", username);
|
|
|
|
return RedirectToAction("Login", "Auth");
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
public IActionResult AccessDenied()
|
|
{
|
|
return View();
|
|
}
|
|
}
|