Selaa lähdekoodia

Authentifizierung und Autorisierung kurz vor Fertigstellung

Arne Diekmann 5 vuotta sitten
vanhempi
commit
ada0d40666
21 muutettua tiedostoa jossa 546 lisäystä ja 50 poistoa
  1. 16 0
      GreenTree.Strohrmann.ERP.Core/Helper/IUserHelper.cs
  2. 36 0
      GreenTree.Strohrmann.ERP.Core/Helper/UserHelper.cs
  3. 96 0
      GreenTree.Strohrmann.ERP.Domain/Migrations/20200622114406_User_Mail.Designer.cs
  4. 37 0
      GreenTree.Strohrmann.ERP.Domain/Migrations/20200622114406_User_Mail.cs
  5. 8 0
      GreenTree.Strohrmann.ERP.Domain/Migrations/ERPDbContextModelSnapshot.cs
  6. 138 0
      GreenTree.Strohrmann.ERP.Services/Authentication/DbContextAuthenticationService.cs
  7. 33 0
      GreenTree.Strohrmann.ERP.Services/Authentication/IAuthenticationService.cs
  8. 7 2
      GreenTree.Strohrmann.ERP.Services/Authorization/AdministrationOptions.cs
  9. 11 23
      GreenTree.Strohrmann.ERP.Services/Authorization/CookieAuthorizationService.cs
  10. 9 4
      GreenTree.Strohrmann.ERP.Services/Authorization/DefaultAuthorizationHandler.cs
  11. 1 1
      GreenTree.Strohrmann.ERP.Services/GreenTree.Strohrmann.ERP.Services.csproj
  12. 39 2
      GreenTree.Strohrmann.ERP.Web/Controllers/AccountController.cs
  13. 3 5
      GreenTree.Strohrmann.ERP.Web/Controllers/HomeController.cs
  14. 12 1
      GreenTree.Strohrmann.ERP.Web/Controllers/RightsController.cs
  15. 6 0
      GreenTree.Strohrmann.ERP.Web/Models/Rights/User/UserModel.cs
  16. 38 6
      GreenTree.Strohrmann.ERP.Web/Startup.cs
  17. 36 4
      GreenTree.Strohrmann.ERP.Web/Validators/LoginValidator.cs
  18. 1 0
      GreenTree.Strohrmann.ERP.Web/Validators/UserValidator.cs
  19. 6 0
      GreenTree.Strohrmann.ERP.Web/Views/Account/Login.cshtml
  20. 10 0
      GreenTree.Strohrmann.ERP.Web/Views/Rights/User/Create.cshtml
  21. 3 2
      GreenTree.Strohrmann.ERP.Web/appsettings.json

+ 16 - 0
GreenTree.Strohrmann.ERP.Core/Helper/IUserHelper.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace GreenTree.Strohrmann.ERP.Core.Helper
+{
+    public interface IUserHelper
+    {
+        /// <summary>
+        /// Generates a MD5 hash from a string
+        /// </summary>
+        /// <param name="str">The string to encrypt.</param>
+        /// <param name="lowerCase">Return hashed string in lower case.</param>
+        public string HashString(string str, bool lowerCase);
+    }
+}

+ 36 - 0
GreenTree.Strohrmann.ERP.Core/Helper/UserHelper.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace GreenTree.Strohrmann.ERP.Core.Helper
+{
+    public class UserHelper : IUserHelper
+    {
+        /// <summary>
+        /// Generates a MD5 hash from a string
+        /// </summary>
+        /// <param name="str">The string to encrypt.</param>
+        /// <param name="lowerCase">Return hashed string in lower case.</param>
+        public string HashString(string str, bool lowerCase)
+        {
+            if (String.IsNullOrEmpty(str))
+                throw new ArgumentNullException("str", "An empty string cannot be encrypted.");
+
+            // Byte array representation of that string
+            var encodedStr = new UTF8Encoding().GetBytes(str);
+
+            // Need MD5 to calculate the hash
+            var hash = ((HashAlgorithm)CryptoConfig.CreateFromName("MD5")).ComputeHash(encodedStr);
+
+            // String representation (similar to UNIX format)
+            var hashedStr = BitConverter.ToString(hash)
+               .Replace("-", string.Empty)
+               .ToLower();
+
+            return lowerCase
+                ? hashedStr.ToLower()
+                : hashedStr;
+        }
+    }
+}

+ 96 - 0
GreenTree.Strohrmann.ERP.Domain/Migrations/20200622114406_User_Mail.Designer.cs

@@ -0,0 +1,96 @@
+// <auto-generated />
+using System;
+using GreenTree.Strohrmann.ERP.Domain.Model;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace GreenTree.Strohrmann.ERP.Domain.Migrations
+{
+    [DbContext(typeof(ERPDbContext))]
+    [Migration("20200622114406_User_Mail")]
+    partial class User_Mail
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "3.1.5")
+                .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.Policy", b =>
+                {
+                    b.Property<string>("Name")
+                        .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+                    b.HasKey("Name");
+
+                    b.ToTable("Policies");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.User", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Accountname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<bool?>("Activated")
+                        .IsRequired()
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("tinyint(1)")
+                        .HasDefaultValue(true);
+
+                    b.Property<DateTime?>("Birthdate")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("Forename")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Lastname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("MailAddress")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Password")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Users");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.UserPolicy", b =>
+                {
+                    b.Property<int>("UserId")
+                        .HasColumnType("int");
+
+                    b.Property<string>("PolicyName")
+                        .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+                    b.HasKey("UserId", "PolicyName");
+
+                    b.ToTable("UserPolicies");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.UserPolicy", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Rights.User", "User")
+                        .WithMany("Policies")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 37 - 0
GreenTree.Strohrmann.ERP.Domain/Migrations/20200622114406_User_Mail.cs

@@ -0,0 +1,37 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace GreenTree.Strohrmann.ERP.Domain.Migrations
+{
+    public partial class User_Mail : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<string>(
+                name: "MailAddress",
+                table: "Users",
+                nullable: false);
+
+            migrationBuilder.AddColumn<string>(
+                name: "Password",
+                table: "Users",
+                nullable: false);
+
+            // Set default password value for initialization
+            migrationBuilder.Sql(
+                "UPDATE Users " +
+                "SET Password = 'a3b9c163f6c520407ff34cfdb83ca5c6' " +
+                "WHERE Password IS NULL");
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "MailAddress",
+                table: "Users");
+
+            migrationBuilder.DropColumn(
+                name: "Password",
+                table: "Users");
+        }
+    }
+}

+ 8 - 0
GreenTree.Strohrmann.ERP.Domain/Migrations/ERPDbContextModelSnapshot.cs

@@ -54,6 +54,14 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
+                    b.Property<string>("MailAddress")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Password")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
                     b.HasKey("Id");
 
                     b.ToTable("Users");

+ 138 - 0
GreenTree.Strohrmann.ERP.Services/Authentication/DbContextAuthenticationService.cs

@@ -0,0 +1,138 @@
+using GreenTree.Strohrmann.ERP.Domain.Model;
+using GreenTree.Strohrmann.ERP.Services.Authorization;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Http;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Strohrmann.ERP.Services.Authentication
+{
+    public class DbContextAuthenticationService : IAuthenticationService
+    {
+        #region DI fields
+
+        // The global DbContext
+        private readonly ERPDbContext _eRPDbContext;
+
+        // The global HttpContext accessor
+        private readonly IHttpContextAccessor _httpContextAccessor;
+
+        // The global administration options
+        private readonly AdministrationOptions _administrationOptions;
+
+        #endregion
+
+        #region Ctor
+
+        /// <summary>
+        /// Initializes a new instance of the DbContextAuthenticationService class
+        /// </summary>
+        /// <param name="eRPDbContext">Global DbContext.</param>
+        /// <param name="httpContextAccessor">Global HTTP context accessor.</param>
+        /// <param name="administrationOptions">Global administration options.</param>
+        public DbContextAuthenticationService(
+            ERPDbContext eRPDbContext,
+            IHttpContextAccessor httpContextAccessor,
+            AdministrationOptions administrationOptions)
+        {
+            _eRPDbContext = eRPDbContext;
+            _httpContextAccessor = httpContextAccessor;
+            _administrationOptions = administrationOptions;
+        }
+
+        #endregion
+
+        #region Implementation
+
+        /// <summary>
+        /// Authenticate and sign user in
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <param name="username">The username.</param>
+        /// <param name="isPersistent">The login persistence.</param>
+        public async void SignIn(string username, bool isPersistent = false)
+        {
+            var user = _eRPDbContext.Users
+                .FirstOrDefault(u => u.Accountname == username || u.MailAddress == username);
+
+            if (user == null)
+                throw new Exception(
+                    String.Format("Der Benutzer \"{0}\" kann nicht gefunden werden.", username));
+
+            var claims = new List<Claim>
+            {
+                new Claim(ClaimTypes.NameIdentifier, user.Accountname),
+                new Claim(ClaimTypes.Name, String.Format("{0}, {1}", user.Lastname, user.Forename)),
+                new Claim(ClaimTypes.Email, user.MailAddress)
+            };
+
+            if (user.Birthdate.HasValue)
+                claims.Add(new Claim(ClaimTypes.DateOfBirth, user.Birthdate.Value.ToString("dd.MM.yyyy")));
+
+            foreach (var policy in user.Policies)
+            {
+                claims.Add(new Claim("Policy", policy.PolicyName));
+            }
+
+            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
+            var principal = new ClaimsPrincipal(identity);
+
+            var authProperties = new AuthenticationProperties
+            {
+                AllowRefresh = true,
+                IsPersistent = isPersistent,
+                IssuedUtc = DateTimeOffset.UtcNow
+            };
+
+            await _httpContextAccessor.HttpContext.SignInAsync(
+                CookieAuthenticationDefaults.AuthenticationScheme,
+                principal,
+                authProperties);
+        }
+
+        /// <summary>
+        /// Authenticate and sign user in
+        /// </summary>
+        /// <param name="username">The username.</param>
+        /// <param name="isPersistent">The login persistence.</param>
+        public async void SignInAdmin(bool isPersistent = false)
+        {
+            var claims = new List<Claim>
+            {
+                new Claim(ClaimTypes.NameIdentifier, _administrationOptions.Administrator),
+                new Claim(ClaimTypes.Name, _administrationOptions.Administrator)
+            };
+
+            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
+            var principal = new ClaimsPrincipal(identity);
+
+            var authProperties = new AuthenticationProperties
+            {
+                AllowRefresh = true,
+                IsPersistent = isPersistent,
+                IssuedUtc = DateTimeOffset.UtcNow
+            };
+
+            await _httpContextAccessor.HttpContext.SignInAsync(
+                CookieAuthenticationDefaults.AuthenticationScheme,
+                principal,
+                authProperties);
+        }
+
+        /// <summary>
+        /// Sign user out
+        /// </summary>
+        /// <param name="httpContext"></param>
+        public async void SignOut()
+        {
+            await _httpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+        }
+
+        #endregion
+    }
+}

+ 33 - 0
GreenTree.Strohrmann.ERP.Services/Authentication/IAuthenticationService.cs

@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Http;
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Text;
+
+namespace GreenTree.Strohrmann.ERP.Services.Authentication
+{
+    public interface IAuthenticationService
+    {
+        /// <summary>
+        /// Authenticate and sign user in
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <param name="username">The username.</param>
+        /// <param name="isPersistent">The login persistence.</param>
+        public void SignIn(string username, bool isPersistent = false);
+
+        /// <summary>
+        /// Authenticate and sign user in
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <param name="username">The username.</param>
+        /// <param name="isPersistent">The login persistence.</param>
+        public void SignInAdmin(bool isPersistent = false);
+
+        /// <summary>
+        /// Sign user out
+        /// </summary>
+        /// <param name="httpContext"></param>
+        public void SignOut();
+    }
+}

+ 7 - 2
GreenTree.Strohrmann.ERP.Services/Authorization/AdministrationOptions.cs

@@ -10,8 +10,13 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
     public class AdministrationOptions : ISingletonObject
     {
         /// <summary>
-        /// The collection of the application super administrators
+        /// The username of the administrator
         /// </summary>
-        public string[] SuperAdministrators { get; set; }
+        public string Administrator { get; set; }
+
+        /// <summary>
+        /// The administrator password
+        /// </summary>
+        public string Password { get; set; }
     }
 }

+ 11 - 23
GreenTree.Strohrmann.ERP.Services/Authorization/DbAuthorizationService.cs → GreenTree.Strohrmann.ERP.Services/Authorization/CookieAuthorizationService.cs

@@ -7,32 +7,13 @@ using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.AspNetCore.Authorization;
 using GreenTree.Strohrmann.ERP.Domain.Model;
+using System.Security.Claims;
 
 namespace GreenTree.Strohrmann.ERP.Services.Authorization
 {
-    public class DbAuthorizationService : IAuthorizationService
+    public class CookieAuthorizationService : IAuthorizationService
     {
-        #region DI fields
-
-        /// <summary>
-        /// The global DbContext
-        /// </summary>
-        private readonly ERPDbContext _erpDbContext;
-
-        #endregion
-
-        #region DI ctor
-
-        /// <summary>
-        /// Initializes a new instance of the DbAuthorizationService class
-        /// </summary>
-        /// <param name="eRPDbContext">The global DbContext.</param>
-        public DbAuthorizationService(ERPDbContext eRPDbContext)
-        {
-            _erpDbContext = eRPDbContext;
-        }
-
-        #endregion
+        #region Implementation
 
         /// <summary>
         /// Check wether the user has a specific policy
@@ -41,7 +22,14 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
         /// <param name="policy">The policy to be checked.</param>
         public bool UserHasPolicy(IIdentity identity, string policy)
         {
-            return true;
+            var claimsIdentity = identity as ClaimsIdentity;
+
+            if (claimsIdentity == null) return false;
+
+            return claimsIdentity.Claims
+                .Any(c => c.Type == "Policy" && c.Value == policy);
         }
+
+        #endregion
     }
 }

+ 9 - 4
GreenTree.Strohrmann.ERP.Services/Authorization/DefaultAuthorizationHandler.cs

@@ -1,4 +1,5 @@
 using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Identity;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
@@ -19,6 +20,8 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
         // The current authorization service
         private readonly IAuthorizationService _authorizationService;
 
+        private readonly IHttpContextAccessor _httpContextAccessor;
+
         #endregion
 
         #region Properties
@@ -39,7 +42,8 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
         /// <param name="administrationOptions">The global administration options.</param>
         public DefaultAuthorizationHandler(
             IAuthorizationService authorizationService,
-            IConfiguration configuration)
+            IConfiguration configuration,
+            IHttpContextAccessor httpContextAccessor)
         {
             _authorizationService = authorizationService;
 
@@ -49,6 +53,7 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
                 throw new Exception("The appsettings.json does not contain administration options.");
 
             Options = administrationOptions;
+            _httpContextAccessor = httpContextAccessor;
         }
 
         #endregion
@@ -63,10 +68,10 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
         /// <returns>Returns a succeeded or failed task if the user is authorized for the required resource.</returns>
         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationPolicy requirement)
         {
-            // Check super admin state of user
-            var isSuperAdmin = Options.SuperAdministrators.Contains(context.User.Identity.Name);
+            // Check admin state of user
+            var isAdmin = context.User.Identity.Name == Options.Administrator;
 
-            if (isSuperAdmin)
+            if (isAdmin)
             {
                 context.Succeed(requirement);
 

+ 1 - 1
GreenTree.Strohrmann.ERP.Services/GreenTree.Strohrmann.ERP.Services.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>

+ 39 - 2
GreenTree.Strohrmann.ERP.Web/Controllers/AccountController.cs

@@ -1,9 +1,14 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Security.Claims;
 using System.Threading.Tasks;
+using GreenTree.Strohrmann.ERP.Core.Helper;
 using GreenTree.Strohrmann.ERP.Domain.Model;
+using GreenTree.Strohrmann.ERP.Services.Authentication;
+using GreenTree.Strohrmann.ERP.Services.Authorization;
 using GreenTree.Strohrmann.ERP.Web.Models.Account;
+using Microsoft.AspNetCore.Authentication.Cookies;
 using Microsoft.AspNetCore.Mvc;
 
 namespace GreenTree.Strohrmann.ERP.Web.Controllers
@@ -15,6 +20,15 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
         // The global DbContext
         private readonly ERPDbContext _eRPDbContext;
 
+        // The global authentication service
+        private readonly IAuthenticationService _authenticationService;
+
+        // The global user helper
+        private readonly IUserHelper _userHelper;
+
+        // The global administation options
+        private readonly AdministrationOptions _administrationOptions;
+
         #endregion
 
         #region Ctor
@@ -23,9 +37,19 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
         /// Initializes a new instance of the AccountController class
         /// </summary>
         /// <param name="eRPDbContext">Global DbContext.</param>
-        public AccountController(ERPDbContext eRPDbContext)
+        /// <param name="authenticationService">Global authentication service.</param>
+        /// <param name="userHelper">Global user helper.</param>
+        /// <param name="administrationOptions">Global administration options.</param>
+        public AccountController(
+            ERPDbContext eRPDbContext,
+            IAuthenticationService authenticationService,
+            IUserHelper userHelper,
+            AdministrationOptions administrationOptions)
         {
             _eRPDbContext = eRPDbContext;
+            _authenticationService = authenticationService;
+            _userHelper = userHelper;
+            _administrationOptions = administrationOptions;
         }
 
         #endregion
@@ -50,11 +74,24 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
         public IActionResult Login(LoginModel login)
         {
             if (!ModelState.IsValid)
+            {
+                login.Password = String.Empty;
+
                 return View("~/Views/Account/Login.cshtml", login);
+            }
 
+            if (login.Username == _administrationOptions.Administrator)
+            {
+                _authenticationService.SignInAdmin(login.StayLoggedIn);
 
+                return RedirectToAction("Index", "Home");
+            }
+            else
+            {
+                _authenticationService.SignIn(login.Username, login.StayLoggedIn);
 
-            return RedirectToAction("Index", "Home");
+                return RedirectToAction("Index", "Home");
+            }
         }
 
         #endregion

+ 3 - 5
GreenTree.Strohrmann.ERP.Web/Controllers/HomeController.cs

@@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Logging;
 using GreenTree.Strohrmann.ERP.Web.Models;
 using GreenTree.Strohrmann.ERP.Domain.Model;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authentication.Cookies;
 
 namespace GreenTree.Strohrmann.ERP.Web.Controllers
 {
@@ -20,16 +22,12 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
             _logger = logger;
         }
 
+        [Authorize]
         public IActionResult Index()
         {
             return View();
         }
 
-        public IActionResult Privacy()
-        {
-            return View();
-        }
-
         [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
         public IActionResult Error()
         {

+ 12 - 1
GreenTree.Strohrmann.ERP.Web/Controllers/RightsController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using GreenTree.Strohrmann.ERP.Core.Domain.Rights;
+using GreenTree.Strohrmann.ERP.Core.Helper;
 using GreenTree.Strohrmann.ERP.Domain.Model;
 using GreenTree.Strohrmann.ERP.Web.Models.Rights.User;
 using Microsoft.AspNetCore.Http;
@@ -17,6 +18,9 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
         // The global DbContext
         private readonly ERPDbContext _eRPDbContext;
 
+        // The global user helper
+        private readonly IUserHelper _userHelper;
+
         #endregion
 
         #region Ctor
@@ -25,9 +29,13 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
         /// Initializes a new instance of the RightsController class
         /// </summary>
         /// <param name="eRPDbContext">Global DbContext.</param>
-        public RightsController(ERPDbContext eRPDbContext)
+        /// <param name="userHelper">Global user helper.</param>
+        public RightsController(
+            ERPDbContext eRPDbContext,
+            IUserHelper userHelper)
         {
             _eRPDbContext = eRPDbContext;
+            _userHelper = userHelper;
         }
 
         #endregion
@@ -74,8 +82,10 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
             var user = new User
             {
                 Accountname = userModel.Accountname,
+                Password = _userHelper.HashString(userModel.Password, true),
                 Forename = userModel.Forename,
                 Lastname = userModel.Lastname,
+                MailAddress = userModel.MailAddress,
                 Birthdate = userModel.Birthdate,
                 Activated = true
             };
@@ -124,6 +134,7 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
             user.Accountname = userModel.Accountname;
             user.Forename = userModel.Forename;
             user.Lastname = userModel.Lastname;
+            user.MailAddress = userModel.MailAddress;
             user.Birthdate = userModel.Birthdate;
             user.Activated = userModel.Activated;
 

+ 6 - 0
GreenTree.Strohrmann.ERP.Web/Models/Rights/User/UserModel.cs

@@ -22,6 +22,12 @@ namespace GreenTree.Strohrmann.ERP.Web.Models.Rights.User
         [Display(Name = "Kontoname")]
         public string Accountname { get; set; }
 
+        /// <summary>
+        /// User password (for creation)
+        /// </summary>
+        [Display(Name = "Passwort")]
+        public string Password { get; set; }
+
         /// <summary>
         /// User forename
         /// </summary>

+ 38 - 6
GreenTree.Strohrmann.ERP.Web/Startup.cs

@@ -4,9 +4,13 @@ using System.Linq;
 using System.Reflection;
 using System.Threading.Tasks;
 using FluentValidation.AspNetCore;
+using GreenTree.Strohrmann.ERP.Core.Helper;
 using GreenTree.Strohrmann.ERP.Domain.Model;
+using GreenTree.Strohrmann.ERP.Services.Authentication;
 using GreenTree.Strohrmann.ERP.Services.Authorization;
 using GreenTree.Strohrmann.ERP.Services.Notification;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
@@ -29,6 +33,7 @@ namespace GreenTree.Strohrmann.ERP.Web
         /// </summary>
         public static readonly string[] _availablePolicies =
         {
+            "ViewDashboard",
             "ViewUser",
             "ChangeUser",
             "DeleteUser"
@@ -52,6 +57,17 @@ namespace GreenTree.Strohrmann.ERP.Web
             // Add option handling
             services.AddOptions();
 
+            // Add the HttpContextAccessor as Singleton
+            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+
+            // Add global administration notification options
+            var administrationOptions = Configuration.GetSection("AdministrationOptions").Get<AdministrationOptions>();
+
+            if (administrationOptions == null)
+                throw new Exception("The appsettings.json does not contain administration options.");
+
+            services.AddSingleton(administrationOptions);
+
             // Add global mail notification options
             var mailNotificationOptions = Configuration.GetSection("MailNotificationOptions").Get<MailNotificationOptions>();
 
@@ -79,12 +95,27 @@ namespace GreenTree.Strohrmann.ERP.Web
                 options.UseLazyLoadingProxies();
             });
 
+            // Add user helper service
+            services.AddScoped<IUserHelper, UserHelper>();
+
             // Add MVC with FluentValidation reference
             services.AddMvc()
               .AddFluentValidation(fv => fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()));
 
             // Add authentication
-            services.AddAuthentication();
+            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
+                .AddCookie(options => 
+                {
+                    options.Cookie.HttpOnly = true;
+                    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
+                    options.Cookie.SameSite = SameSiteMode.Strict;
+                    options.LoginPath = "/Account/Login";
+                    options.LogoutPath = "/Account/Logoff";
+                    options.ExpireTimeSpan = new TimeSpan(0, 24, 0, 0);
+                });
+
+            // Add the default custom authentication service
+            services.AddScoped<Services.Authentication.IAuthenticationService, DbContextAuthenticationService>();
 
             // Add the default authorization handler
             services.AddScoped<IAuthorizationHandler, DefaultAuthorizationHandler>();
@@ -95,16 +126,15 @@ namespace GreenTree.Strohrmann.ERP.Web
                 {
                     options.AddPolicy(policy, a =>
                     {
+                        a.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme);
+                        a.RequireAuthenticatedUser();
                         a.AddRequirements(new DefaultAuthorizationPolicy(policy));
                     });
                 }
             });
 
-            // Add the Windows authorization service
-            services.AddScoped<Services.Authorization.IAuthorizationService, DbAuthorizationService>();
-
-            // Add the HttpContextAccessor as Singleton
-            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+            // Add the DbContext custom authorization service
+            services.AddScoped<Services.Authorization.IAuthorizationService, CookieAuthorizationService>();
         }
 
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -123,6 +153,8 @@ namespace GreenTree.Strohrmann.ERP.Web
             app.UseRouting();
 
             app.UseAuthorization();
+            app.UseAuthentication();
+            app.UseCookiePolicy();
 
             app.UseEndpoints(endpoints =>
             {

+ 36 - 4
GreenTree.Strohrmann.ERP.Web/Validators/LoginValidator.cs

@@ -1,5 +1,7 @@
 using FluentValidation;
+using GreenTree.Strohrmann.ERP.Core.Helper;
 using GreenTree.Strohrmann.ERP.Domain.Model;
+using GreenTree.Strohrmann.ERP.Services.Authorization;
 using GreenTree.Strohrmann.ERP.Web.Models.Account;
 using System;
 using System.Collections.Generic;
@@ -15,6 +17,12 @@ namespace GreenTree.Strohrmann.ERP.Web.Validators
 		// The global DbContext
 		private readonly ERPDbContext _eRPDbContext;
 
+		// The global user helper
+		private readonly IUserHelper _userHelper;
+
+		// The global administration options
+		private readonly AdministrationOptions _administrationOptions;
+
 		#endregion
 
 		#region Ctor
@@ -23,21 +31,39 @@ namespace GreenTree.Strohrmann.ERP.Web.Validators
 		/// Initializes a new instance of the LoginValidator class
 		/// </summary>
 		/// <param name="eRPDbContext">Global DbContext.</param>
-		public LoginValidator(ERPDbContext eRPDbContext)
+		/// <param name="userHelper">Global user helper.</param>
+		public LoginValidator(
+			ERPDbContext eRPDbContext,
+			IUserHelper userHelper,
+			AdministrationOptions administrationOptions)
 		{
 			_eRPDbContext = eRPDbContext;
+			_userHelper = userHelper;
+			_administrationOptions = administrationOptions;
 
 			RuleFor(x => x.Username)
 				.NotEmpty()
 				.WithMessage("Benutzername erforderlich.")
-				.Must(a => _eRPDbContext.Users.Any(u => u.Accountname == a || u.MailAddress == a))
-				.WithMessage("Benutzername nicht gefunden.");
+				.Custom((a, context) =>
+				{
+					if (a == _administrationOptions.Administrator)
+						return;
+
+					if (!_eRPDbContext.Users.Any(u => u.Accountname == a || u.MailAddress == a))
+					{
+						context.AddFailure("Benutzername nicht gefunden.");
+						return;
+					}
+				});
 
 			RuleFor(x => x.Password)
 				.NotEmpty()
 				.WithMessage("Passwort erforderlich.")
 				.Custom((p, context) =>
 				{
+					if (String.IsNullOrEmpty(p))
+						return;
+
 					var model = context.InstanceToValidate as LoginModel;
 
 					if (model == null)
@@ -46,6 +72,12 @@ namespace GreenTree.Strohrmann.ERP.Web.Validators
 						return;
 					}
 
+					if (model.Username == administrationOptions.Administrator &&
+						_userHelper.HashString(p, false) == _administrationOptions.Password)
+					{
+						return;
+					}
+
 					var user = _eRPDbContext.Users
 						.FirstOrDefault(u => u.Accountname == model.Username || u.MailAddress == model.Username);
 
@@ -55,7 +87,7 @@ namespace GreenTree.Strohrmann.ERP.Web.Validators
 						return;
 					}
 
-					if (user.Password != p.ToString())
+					if (user.Password != _userHelper.HashString(p, true))
 						context.AddFailure("Kennwort falsch.");
 
 					return;

+ 1 - 0
GreenTree.Strohrmann.ERP.Web/Validators/UserValidator.cs

@@ -1,5 +1,6 @@
 using FluentValidation;
 using GreenTree.Strohrmann.ERP.Domain.Model;
+using GreenTree.Strohrmann.ERP.Services.Authorization;
 using GreenTree.Strohrmann.ERP.Web.Models.Rights.User;
 using Microsoft.AspNetCore.Identity;
 using System;

+ 6 - 0
GreenTree.Strohrmann.ERP.Web/Views/Account/Login.cshtml

@@ -19,6 +19,12 @@
             @Html.LabelFor(model => model.StayLoggedIn)
         </label>
     </div>
+    <div class="mb-2 mt-2">
+        <span asp-validation-for="Username" class="text-danger"></span>
+    </div>
+    <div class="mb-2 mt-2">
+        <span asp-validation-for="Password" class="text-danger"></span>
+    </div>
     <button class="btn btn-lg btn-primary btn-block" type="submit">Anmelden</button>
     <p class="mt-5 mb-3 text-muted">© 2020 - GreenTree Studios</p>
 </form>

+ 10 - 0
GreenTree.Strohrmann.ERP.Web/Views/Rights/User/Create.cshtml

@@ -17,6 +17,11 @@
                 <input asp-for="Accountname" class="form-control" />
                 <span asp-validation-for="Accountname" class="text-danger"></span>
             </div>
+            <div class="form-group">
+                <label asp-for="Password" class="control-label"></label>
+                <input asp-for="Password" class="form-control" />
+                <span asp-validation-for="Password" class="text-danger"></span>
+            </div>
             <div class="form-group">
                 <label asp-for="Forename" class="control-label"></label>
                 <input asp-for="Forename" class="form-control" />
@@ -27,6 +32,11 @@
                 <input asp-for="Lastname" class="form-control" />
                 <span asp-validation-for="Lastname" class="text-danger"></span>
             </div>
+            <div class="form-group">
+                <label asp-for="MailAddress" class="control-label"></label>
+                <input asp-for="MailAddress" type="email" class="form-control" />
+                <span asp-validation-for="MailAddress" class="text-danger"></span>
+            </div>
             <div class="form-group">
                 <label asp-for="Birthdate" class="control-label"></label>
                 <input asp-for="Birthdate" type="date" class="form-control" />

+ 3 - 2
GreenTree.Strohrmann.ERP.Web/appsettings.json

@@ -8,7 +8,7 @@
     },
     "AllowedHosts": "*",
     "ConnectionStrings": {
-        "ERPDatabase": "Server=arne-nas;Port=13306;Database=StrohrmannERP;User Id=root;Password=strohrmann123!;"
+        "ERPDatabase": "Server=lynx-solutions.org;Port=13306;Database=StrohrmannERP;User Id=root;Password=strohrmann123!;"
     },
     "SessionOptions": {
         "IdleTimeout": "01:00:00",
@@ -17,7 +17,8 @@
         }
     },
     "AdministrationOptions": {
-        "SuperAdministrators": [ "PORTAIT\\anw0486m", "ARNE-WIN10\\ArneD" ]
+        "Administrator": "admin",
+        "Password": "2fbc1282c8d00d1b0726a5a00d13214f"
     },
     "MailNotificationOptions": {
         "From": "no-reply@Strohrmann-ERP",