From b789a8f8bfb1bce06275d34c19e5412348e1d0dc Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 20 Feb 2022 19:04:11 +0100 Subject: [PATCH] Added backend --- .gitignore | 66 ++ LICENSE | 21 + README.md | 2 + gswi.Configuration/APISettings.cs | 10 + gswi.Configuration/APIVersionSettings.cs | 15 + .../AuthentificationSettings.cs | 11 + gswi.Configuration/ConfigurationExtensions.cs | 36 ++ gswi.Configuration/Constants.cs | 7 + gswi.Configuration/DatabaseSettings.cs | 8 + gswi.Configuration/EMailSettings.cs | 14 + gswi.Configuration/FrontendSettings.cs | 7 + gswi.Configuration/HostExtensions.cs | 115 ++++ gswi.Configuration/gswi.Configuration.csproj | 11 + gswi.CredentialManager/Base64.cs | 25 + gswi.CredentialManager/Program.cs | 89 +++ .../gswi.CredentialManager.csproj | 14 + gswi.Data/DatabaseContext.cs | 73 +++ gswi.Data/DesignTimeContextFactory.cs | 22 + gswi.Data/LinqExtension.cs | 30 + .../20210213133159_2021_03_10.Designer.cs | 291 +++++++++ .../Migrations/20210213133159_2021_03_10.cs | 249 ++++++++ .../20210217201310_2021_03_13.Designer.cs | 294 +++++++++ .../Migrations/20210217201310_2021_03_13.cs | 23 + .../20210220140309_2021_03_14.Designer.cs | 291 +++++++++ .../Migrations/20210220140309_2021_03_14.cs | 53 ++ .../DatabaseContextModelSnapshot.cs | 289 +++++++++ .../Repositories/AuthUserRepositoryImpl.cs | 125 ++++ gswi.Data/gswi.Data.csproj | 23 + .../Repositories/IAuthUserRepository.cs | 20 + gswi.Interface/Repositories/IUnitOfWork.cs | 11 + gswi.Interface/Services/IAuthService.cs | 28 + gswi.Interface/gswi.Interface.csproj | 12 + gswi.Model/AuthRoles.cs | 10 + gswi.Model/AuthUser.cs | 34 + gswi.Model/DTOs/AdminUpdateUserDTO.cs | 11 + gswi.Model/DTOs/ApiVersionDTO.cs | 11 + gswi.Model/DTOs/AuthUserDTO.cs | 27 + gswi.Model/DTOs/EMailStringDTO.cs | 9 + .../DTOs/GetFilteredAuthUsersResultDTO.cs | 10 + gswi.Model/DTOs/ResetPasswordDTO.cs | 10 + gswi.Model/DTOs/SettingsDTO.cs | 22 + gswi.Model/DTOs/TokenDTO.cs | 8 + gswi.Model/DTOs/UpdateUserDTO.cs | 10 + gswi.Model/DTOs/UserDTO.cs | 13 + gswi.Model/Filter/ErrorDTO.cs | 21 + gswi.Model/Filter/ServiceErrorCode.cs | 21 + gswi.Model/Filter/ServiceException.cs | 23 + gswi.Model/IAutoGenerateDateFields.cs | 10 + gswi.Model/gswi.Model.csproj | 8 + gswi.SMTP.Interface/ISMTPClient.cs | 10 + .../gswi.SMTP.Interface.csproj | 11 + gswi.SMTP.Model/EMail.cs | 11 + gswi.SMTP.Model/gswi.SMTP.Model.csproj | 7 + gswi.SMTP.Service/SMTPClientImpl.cs | 80 +++ gswi.SMTP.Service/gswi.SMTP.Service.csproj | 15 + gswi.Service/AuthServiceImpl.cs | 604 ++++++++++++++++++ gswi.Service/gswi.Service.csproj | 19 + gswi.Share.Common/AuthUserSelectCriterion.cs | 11 + gswi.Share.Common/SelectCriterion.cs | 10 + gswi.Share.Common/gswi.Share.Common.csproj | 11 + gswi.SignalR/NotifyHub.cs | 9 + gswi.SignalR/gswi.SignalR.csproj | 11 + gswi.sln | 127 ++++ gswi/Controllers/AuthController.cs | 148 +++++ gswi/Controllers/GUIController.cs | 92 +++ gswi/DataSeeder.cs | 76 +++ .../Filters/CustomExceptionFilterAttribute.cs | 55 ++ gswi/HostExtensions.cs | 30 + gswi/JSONConverter/JsonCreationConverter.cs | 54 ++ gswi/Program.cs | 43 ++ gswi/Properties/launchSettings.json | 28 + gswi/Startup.cs | 186 ++++++ gswi/appsettings.json | 18 + gswi/gswi.csproj | 43 ++ gswi/nlog-development.config | 26 + gswi/nlog-production.config | 26 + gswi/nlog-staging.config | 26 + gswi/update-version.ps1 | 52 ++ 78 files changed, 4382 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 gswi.Configuration/APISettings.cs create mode 100644 gswi.Configuration/APIVersionSettings.cs create mode 100644 gswi.Configuration/AuthentificationSettings.cs create mode 100644 gswi.Configuration/ConfigurationExtensions.cs create mode 100644 gswi.Configuration/Constants.cs create mode 100644 gswi.Configuration/DatabaseSettings.cs create mode 100644 gswi.Configuration/EMailSettings.cs create mode 100644 gswi.Configuration/FrontendSettings.cs create mode 100644 gswi.Configuration/HostExtensions.cs create mode 100644 gswi.Configuration/gswi.Configuration.csproj create mode 100644 gswi.CredentialManager/Base64.cs create mode 100644 gswi.CredentialManager/Program.cs create mode 100644 gswi.CredentialManager/gswi.CredentialManager.csproj create mode 100644 gswi.Data/DatabaseContext.cs create mode 100644 gswi.Data/DesignTimeContextFactory.cs create mode 100644 gswi.Data/LinqExtension.cs create mode 100644 gswi.Data/Migrations/20210213133159_2021_03_10.Designer.cs create mode 100644 gswi.Data/Migrations/20210213133159_2021_03_10.cs create mode 100644 gswi.Data/Migrations/20210217201310_2021_03_13.Designer.cs create mode 100644 gswi.Data/Migrations/20210217201310_2021_03_13.cs create mode 100644 gswi.Data/Migrations/20210220140309_2021_03_14.Designer.cs create mode 100644 gswi.Data/Migrations/20210220140309_2021_03_14.cs create mode 100644 gswi.Data/Migrations/DatabaseContextModelSnapshot.cs create mode 100644 gswi.Data/Repositories/AuthUserRepositoryImpl.cs create mode 100644 gswi.Data/gswi.Data.csproj create mode 100644 gswi.Interface/Repositories/IAuthUserRepository.cs create mode 100644 gswi.Interface/Repositories/IUnitOfWork.cs create mode 100644 gswi.Interface/Services/IAuthService.cs create mode 100644 gswi.Interface/gswi.Interface.csproj create mode 100644 gswi.Model/AuthRoles.cs create mode 100644 gswi.Model/AuthUser.cs create mode 100644 gswi.Model/DTOs/AdminUpdateUserDTO.cs create mode 100644 gswi.Model/DTOs/ApiVersionDTO.cs create mode 100644 gswi.Model/DTOs/AuthUserDTO.cs create mode 100644 gswi.Model/DTOs/EMailStringDTO.cs create mode 100644 gswi.Model/DTOs/GetFilteredAuthUsersResultDTO.cs create mode 100644 gswi.Model/DTOs/ResetPasswordDTO.cs create mode 100644 gswi.Model/DTOs/SettingsDTO.cs create mode 100644 gswi.Model/DTOs/TokenDTO.cs create mode 100644 gswi.Model/DTOs/UpdateUserDTO.cs create mode 100644 gswi.Model/DTOs/UserDTO.cs create mode 100644 gswi.Model/Filter/ErrorDTO.cs create mode 100644 gswi.Model/Filter/ServiceErrorCode.cs create mode 100644 gswi.Model/Filter/ServiceException.cs create mode 100644 gswi.Model/IAutoGenerateDateFields.cs create mode 100644 gswi.Model/gswi.Model.csproj create mode 100644 gswi.SMTP.Interface/ISMTPClient.cs create mode 100644 gswi.SMTP.Interface/gswi.SMTP.Interface.csproj create mode 100644 gswi.SMTP.Model/EMail.cs create mode 100644 gswi.SMTP.Model/gswi.SMTP.Model.csproj create mode 100644 gswi.SMTP.Service/SMTPClientImpl.cs create mode 100644 gswi.SMTP.Service/gswi.SMTP.Service.csproj create mode 100644 gswi.Service/AuthServiceImpl.cs create mode 100644 gswi.Service/gswi.Service.csproj create mode 100644 gswi.Share.Common/AuthUserSelectCriterion.cs create mode 100644 gswi.Share.Common/SelectCriterion.cs create mode 100644 gswi.Share.Common/gswi.Share.Common.csproj create mode 100644 gswi.SignalR/NotifyHub.cs create mode 100644 gswi.SignalR/gswi.SignalR.csproj create mode 100644 gswi.sln create mode 100644 gswi/Controllers/AuthController.cs create mode 100644 gswi/Controllers/GUIController.cs create mode 100644 gswi/DataSeeder.cs create mode 100644 gswi/Filters/CustomExceptionFilterAttribute.cs create mode 100644 gswi/HostExtensions.cs create mode 100644 gswi/JSONConverter/JsonCreationConverter.cs create mode 100644 gswi/Program.cs create mode 100644 gswi/Properties/launchSettings.json create mode 100644 gswi/Startup.cs create mode 100644 gswi/appsettings.json create mode 100644 gswi/gswi.csproj create mode 100644 gswi/nlog-development.config create mode 100644 gswi/nlog-production.config create mode 100644 gswi/nlog-staging.config create mode 100644 gswi/update-version.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1b01a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +*/dist +*/tmp +*/out-tsc +# Only exists if Bazel was run +*/bazel-out + +# dependencies +*node_modules* + +# profiling files +*chrome-profiler-events*.json +*speed-measure-plugin*.json + +# IDEs and editors +*/.idea +*.project +*.classpath +*.c9/ +*.launch +*.settings/ +*.sublime-workspace + +# IDE - VSCode +*.vscode/* +!*.vscode/settings.json +!*.vscode/tasks.json +!*.vscode/launch.json +!*.vscode/extensions.json +*.history/* + +# misc +*/.sass-cache +*/connect.lock +*/coverage +*/libpeerconnection.log +*npm-debug.log +*yarn-error.log +*testem.log +*/typings + +# System Files +*.DS_Store +*Thumbs.db + +# .Net Env +*Debug* +obj +logs +-nlog.txt +.vs +.vscode + +# Python Env +*__pycache__* +*.pyc +*.idea* +*.log + +# angular & .net +dist +bin +.angular +appsettings.*.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c1f628d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Sven Heidemann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9263e33 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# GSWI Server + diff --git a/gswi.Configuration/APISettings.cs b/gswi.Configuration/APISettings.cs new file mode 100644 index 0000000..9a5e347 --- /dev/null +++ b/gswi.Configuration/APISettings.cs @@ -0,0 +1,10 @@ +using System; + +namespace gswi.Configuration +{ + public class APISettings + { + public bool RedirectToHTTPS { get; set; } + public APIVersionSettings ApiVersion { get; set; } + } +} diff --git a/gswi.Configuration/APIVersionSettings.cs b/gswi.Configuration/APIVersionSettings.cs new file mode 100644 index 0000000..0c1e620 --- /dev/null +++ b/gswi.Configuration/APIVersionSettings.cs @@ -0,0 +1,15 @@ +using System; + +namespace gswi.Configuration +{ + public class APIVersionSettings + { + public string Major { get; set; } + public string Minor { get; set; } + public string Micro { get; set; } + + public override string ToString() { + return $"{this.Major}.{this.Minor}.{this.Micro}"; + } + } +} diff --git a/gswi.Configuration/AuthentificationSettings.cs b/gswi.Configuration/AuthentificationSettings.cs new file mode 100644 index 0000000..abafccd --- /dev/null +++ b/gswi.Configuration/AuthentificationSettings.cs @@ -0,0 +1,11 @@ +namespace gswi.Configuration +{ + public class AuthentificationSettings + { + public string SecretKey { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + public int TokenExpireTime { get; set; } + public int RefreshTokenExpireTime { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Configuration/ConfigurationExtensions.cs b/gswi.Configuration/ConfigurationExtensions.cs new file mode 100644 index 0000000..ae31c6a --- /dev/null +++ b/gswi.Configuration/ConfigurationExtensions.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Configuration; +using System; + +namespace gswi.Configuration { + public static class ConfigurationExtensions { + public static TSetting GetSetting(this IConfiguration config, string sectionName) + where TSetting : new() { + var settings = new TSetting(); + config.GetSection(sectionName).Bind(settings); + return settings; + } + + public static string GetDbConnectionString(this IConfiguration config, string sectionName) { + var connectionString = config[$"{sectionName}:ConnectionString"]; + + if (string.IsNullOrEmpty(connectionString)) + throw new Exception($"ConnectionString is not set! SectionName:{sectionName}"); + + var dbCredentials = config[$"{sectionName}:Credentials"]; + + if (!string.IsNullOrEmpty(dbCredentials)) + connectionString += dbCredentials; + + return connectionString; + } + + public static string GetCustomer(this IConfiguration config) { + return config["CUSTOMER"]; + } + + public static string GetComputerName(this IConfiguration config) { + return config["COMPUTERNAME"]; + } + + } +} diff --git a/gswi.Configuration/Constants.cs b/gswi.Configuration/Constants.cs new file mode 100644 index 0000000..20e2cc8 --- /dev/null +++ b/gswi.Configuration/Constants.cs @@ -0,0 +1,7 @@ +namespace gswi.Configuration { + public static class Constants { + public const string CustomerEnvironmentVariable = "CUSTOMER"; + public const string ComputerNameEnvironmentVariable = "COMPUTERNAME"; + public const string CustomersDirectoryName = "customers"; + } +} diff --git a/gswi.Configuration/DatabaseSettings.cs b/gswi.Configuration/DatabaseSettings.cs new file mode 100644 index 0000000..36deb85 --- /dev/null +++ b/gswi.Configuration/DatabaseSettings.cs @@ -0,0 +1,8 @@ +namespace gswi.Configuration +{ + public class DatabaseSettings + { + public string ConnectionString { get; set; } + public string Credentials { get; set; } + } +} diff --git a/gswi.Configuration/EMailSettings.cs b/gswi.Configuration/EMailSettings.cs new file mode 100644 index 0000000..19ca885 --- /dev/null +++ b/gswi.Configuration/EMailSettings.cs @@ -0,0 +1,14 @@ +namespace gswi.Configuration +{ + public class EMailSettings + { + public string FromName { get; set; } + public string FromAddress { get; set; } + + public string MailServerAddress { get; set; } + public int MailServerPort { get; set; } + + public string Username { get; set; } + public string Credentials { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Configuration/FrontendSettings.cs b/gswi.Configuration/FrontendSettings.cs new file mode 100644 index 0000000..d484f67 --- /dev/null +++ b/gswi.Configuration/FrontendSettings.cs @@ -0,0 +1,7 @@ +namespace gswi.Configuration +{ + public class FrontendSettings + { + public string URL { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Configuration/HostExtensions.cs b/gswi.Configuration/HostExtensions.cs new file mode 100644 index 0000000..84824fd --- /dev/null +++ b/gswi.Configuration/HostExtensions.cs @@ -0,0 +1,115 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.EventLog; +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace gswi.Configuration { + public static class HostExtensions { + // copied from https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Host.cs, + // modified to use customer environment + public static IHostBuilder CreateBuilder(string[] args) { + var builder = new HostBuilder(); + + builder.UseContentRoot(Directory.GetCurrentDirectory()); // UseWindowsService overrides content root to AppContext.BaseDirectory + builder.ConfigureHostConfiguration(config => { + config.AddEnvironmentVariables(prefix: "DOTNET_"); + config.AddEnvironmentVariables(prefix: "LOGINCOUNTER_"); + if (args != null) { + config.AddCommandLine(args); + } + }); + + builder.ConfigureAppConfiguration((hostingContext, config) => { + var env = hostingContext.HostingEnvironment; + var customerEnv = hostingContext.Configuration.GetValue(Constants.CustomerEnvironmentVariable); + //var customerEnv = Environment.GetEnvironmentVariable(Constants.CustomerEnvironmentVariable); + if (!string.IsNullOrEmpty(customerEnv)) + config.SetBasePath(Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, Constants.CustomersDirectoryName, customerEnv)); + + config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); + config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true); + + var computerName = Environment.GetEnvironmentVariable(Constants.ComputerNameEnvironmentVariable); + if (!string.IsNullOrEmpty(computerName)) + config.AddJsonFile($"appsettings.{computerName}.json", optional: true, reloadOnChange: true); + + if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName)) { + var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); + if (appAssembly != null) { + config.AddUserSecrets(appAssembly, optional: true); + } + } + + config.AddEnvironmentVariables(); + config.AddEnvironmentVariables("IOTHUB_"); + + if (args != null) { + config.AddCommandLine(args); + } + }) + .ConfigureLogging((hostingContext, logging) => { + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + // IMPORTANT: This needs to be added *before* configuration is loaded, this lets + // the defaults be overridden by the configuration. + if (isWindows) { + // Default the EventLogLoggerProvider to warning or above + logging.AddFilter(level => level >= LogLevel.Warning); + } + + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + logging.AddDebug(); + logging.AddEventSourceLogger(); + + if (isWindows) { + // Add the EventLogLoggerProvider on windows machines + logging.AddEventLog(); + } + }) + .UseDefaultServiceProvider((context, options) => { + var isDevelopment = context.HostingEnvironment.IsDevelopment(); + options.ValidateScopes = isDevelopment; + options.ValidateOnBuild = isDevelopment; + }); + + return builder; + } + + public static void LogHostingEnvironment(this IHost host) { + var loggerFactory = host.Services.GetRequiredService(); + var configuration = host.Services.GetRequiredService(); + var hostEnvironment = host.Services.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + logger.LogInformation("Running Host..."); + logger.LogInformation($"EnvironmentName:{hostEnvironment.EnvironmentName}"); + logger.LogInformation($"ContentRootPath:{hostEnvironment.ContentRootPath}"); + logger.LogInformation($"CurrentDirectory:{Environment.CurrentDirectory}"); + logger.LogInformation($"ApplicationName:{hostEnvironment.ApplicationName}"); + logger.LogInformation($"Customer:{configuration.GetCustomer()}"); + logger.LogInformation($"ComputerName:{configuration.GetComputerName()}"); + } + + public static void LogStartError(Exception ex) { + var logDir = Path.Combine(AppContext.BaseDirectory, "logs"); + if (!Directory.Exists(logDir)) + Directory.CreateDirectory(logDir); + var logFile = Path.Combine(logDir, "start_error.txt"); + File.AppendAllText(logFile, DateTime.Now.ToString() + ": " + ex.ToString() + Environment.NewLine); + } + + public static void ChangeCurrentDirToProjectDir() { + var currentDir = Directory.GetCurrentDirectory(); + var ix = currentDir.IndexOf("\\bin"); // goto project dir, if we are running from VS + if (ix >= 0) { + var newDir = currentDir.Substring(0, ix); + Directory.SetCurrentDirectory(newDir); + } + } + } +} diff --git a/gswi.Configuration/gswi.Configuration.csproj b/gswi.Configuration/gswi.Configuration.csproj new file mode 100644 index 0000000..0a8a9b3 --- /dev/null +++ b/gswi.Configuration/gswi.Configuration.csproj @@ -0,0 +1,11 @@ + + + net6.0 + + + + + + + + \ No newline at end of file diff --git a/gswi.CredentialManager/Base64.cs b/gswi.CredentialManager/Base64.cs new file mode 100644 index 0000000..bd6e4cb --- /dev/null +++ b/gswi.CredentialManager/Base64.cs @@ -0,0 +1,25 @@ +using System; + +namespace gswi.CredentialManager +{ + public static class Base64 + { + public static string Encode(string credentials) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(credentials); + return System.Convert.ToBase64String(plainTextBytes); + } + + public static string Decode(string credentials) + { + var base64EncodedBytes = System.Convert.FromBase64String(credentials); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + + public static string DecodeConnectionString(string connectionString, string credentials) + { + string decodedCredentials = Decode(credentials); + return connectionString += $"password={decodedCredentials};"; + } + } +} \ No newline at end of file diff --git a/gswi.CredentialManager/Program.cs b/gswi.CredentialManager/Program.cs new file mode 100644 index 0000000..e8f2235 --- /dev/null +++ b/gswi.CredentialManager/Program.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using gswi.Configuration; + +namespace gswi.CredentialManager +{ + class Program + { + static IConfiguration Configuration { get; set; } + + static void Menu() + { + bool end = false; + while (!end) + { + Console.Clear(); + Console.WriteLine("Credential Manager: \n"); + Console.WriteLine("[1] Encode"); + Console.WriteLine("[2] Decode"); + Console.WriteLine("[x] Exit"); + Console.Write("\n> "); + string input = Console.ReadLine(); + switch (input) + { + case "1": + Console.Clear(); + Encode(); + break; + + case "2": + Console.Clear(); + Decode(); + break; + + case "x": + end = true; + Continue(); + break; + + case "X": + end = true; + Continue(); + break; + + default: + Console.WriteLine("Invalid input"); + Continue(); + break; + } + } + } + + static void Continue() + { + Console.WriteLine("\n\nPress any key to continue..."); + Console.ReadLine(); + } + + static void Main(string[] args) + { + Menu(); + } + + static void Encode() + { + Console.Write("String to encode: "); + string input = Console.ReadLine(); + Console.WriteLine($"{input} encoded with Base64 is: {Base64.Encode(input)}"); + Continue(); + } + + static void Decode() + { + Console.Write("String to decode: "); + string input = Console.ReadLine(); + Console.WriteLine($"{input} decoded with Base64 is: {Base64.Decode(input)}"); + Continue(); + } + + + protected static SettingType GetTypedSettingsFromSection(string sectionName) where SettingType : new() + { + var settings = new SettingType(); + Configuration.GetSection(sectionName).Bind(settings); + return settings; + } + } +} diff --git a/gswi.CredentialManager/gswi.CredentialManager.csproj b/gswi.CredentialManager/gswi.CredentialManager.csproj new file mode 100644 index 0000000..97578fd --- /dev/null +++ b/gswi.CredentialManager/gswi.CredentialManager.csproj @@ -0,0 +1,14 @@ + + + Exe + net6.0 + + + + + + + + + + \ No newline at end of file diff --git a/gswi.Data/DatabaseContext.cs b/gswi.Data/DatabaseContext.cs new file mode 100644 index 0000000..a56c008 --- /dev/null +++ b/gswi.Data/DatabaseContext.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using gswi.Interface.Repositories; +using gswi.Model; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace gswi.Data +{ + public class DatabaseContext : DbContext, IUnitOfWork + { + public DatabaseContext(DbContextOptions options) : base(options) { } + + public DbSet AuthUsers { get; set; } + + public Task SaveChangesAsync() + { + var changedList = ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified); + foreach (var item in changedList) + { + item.Entity.LastModifiedOn = DateTime.UtcNow; + if (item.State == EntityState.Added) + item.Entity.CreatedOn = DateTime.UtcNow; + } + return base.SaveChangesAsync(); + } + + public override int SaveChanges() + { + var changedList = ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified); + foreach (var item in changedList) + { + item.Entity.LastModifiedOn = DateTime.UtcNow; + if (item.State == EntityState.Added) + item.Entity.CreatedOn = DateTime.UtcNow; + } + return base.SaveChanges(); + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // Datum der letzten Änderung, etc. setzen + DateTimeOffset now = DateTimeOffset.UtcNow; + var changedList = ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified); + foreach (var item in changedList) + { + item.Entity.LastModifiedOn = now; + if (item.State == EntityState.Added) + item.Entity.CreatedOn = now; + } + return base.SaveChangesAsync(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasIndex(au => au.EMail) + .IsUnique(true); + + modelBuilder.Entity() + .HasIndex(au => au.ConfirmationId) + .IsUnique(true); + + modelBuilder.Entity() + .HasIndex(au => au.ForgotPasswordId) + .IsUnique(true); + } + } +} diff --git a/gswi.Data/DesignTimeContextFactory.cs b/gswi.Data/DesignTimeContextFactory.cs new file mode 100644 index 0000000..103f7ad --- /dev/null +++ b/gswi.Data/DesignTimeContextFactory.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using System; +using System.Collections.Generic; +using System.Text; + +namespace gswi.Data +{ + public class DesignTimeContextFactory : IDesignTimeDbContextFactory + { + public DatabaseContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseMySql( + "server=localhost;user=sh_login_counter;password=mZaKBHY1lhzQoCAv;database=sh_login_counter", + new MySqlServerVersion(new Version(10, 3, 32)) + ); + + return new DatabaseContext(optionsBuilder.Options); + } + } +} diff --git a/gswi.Data/LinqExtension.cs b/gswi.Data/LinqExtension.cs new file mode 100644 index 0000000..6321059 --- /dev/null +++ b/gswi.Data/LinqExtension.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace gswi.Data +{ + public static class LinqExtension + { + public static IQueryable OrderByMember(this IQueryable q, string SortField) + { + var param = Expression.Parameter(typeof(T), "p"); + var prop = Expression.Property(param, SortField); + var exp = Expression.Lambda(prop, param); + Type[] types = new Type[] { q.ElementType, exp.Body.Type }; + var mce = Expression.Call(typeof(Queryable), "OrderBy", types, q.Expression, exp); + return q.Provider.CreateQuery(mce); + } + + public static IQueryable OrderByMemberDescending(this IQueryable q, string SortField) + { + var param = Expression.Parameter(typeof(T), "p"); + var prop = Expression.Property(param, SortField); + var exp = Expression.Lambda(prop, param); + Type[] types = new Type[] { q.ElementType, exp.Body.Type }; + var mce = Expression.Call(typeof(Queryable), "OrderByDescending", types, q.Expression, exp); + return q.Provider.CreateQuery(mce); + } + + } +} \ No newline at end of file diff --git a/gswi.Data/Migrations/20210213133159_2021_03_10.Designer.cs b/gswi.Data/Migrations/20210213133159_2021_03_10.Designer.cs new file mode 100644 index 0000000..3f0d6e2 --- /dev/null +++ b/gswi.Data/Migrations/20210213133159_2021_03_10.Designer.cs @@ -0,0 +1,291 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using gswi.Data; + +namespace gswi.Data.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210213133159_2021_03_10")] + partial class _2021_03_10 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("gswi.Model.AuthUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ConfirmationId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("EMail") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("FirstName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("ForgotPasswordId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Password") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshToken") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ConfirmationId") + .IsUnique(); + + b.HasIndex("EMail") + .IsUnique(); + + b.HasIndex("ForgotPasswordId") + .IsUnique(); + + b.ToTable("AuthUsers"); + }); + + modelBuilder.Entity("gswi.Model.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.Property("IPAddress") + .HasColumnType("varbinary(1400)") + .HasMaxLength(1400); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.Property("OSId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DomainId"); + + b.HasIndex("OSId"); + + b.HasIndex("DomainId", "Name") + .IsUnique(); + + b.HasIndex("IPAddress", "Name") + .IsUnique(); + + b.ToTable("Hosts"); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Time") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("HostId"); + + b.HasIndex("UserId"); + + b.ToTable("Logins"); + }); + + modelBuilder.Entity("gswi.Model.OperatingSystem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OperatingSystems"); + }); + + modelBuilder.Entity("gswi.Model.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Discriminator", "Name") + .IsUnique(); + + b.ToTable("Users"); + + b.HasDiscriminator("Discriminator").HasValue("User"); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.HasIndex("DomainId"); + + b.HasDiscriminator().HasValue("DomainUser"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.HasIndex("HostId"); + + b.HasDiscriminator().HasValue("HostUser"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Hosts") + .HasForeignKey("DomainId"); + + b.HasOne("gswi.Model.OperatingSystem", "OS") + .WithMany() + .HasForeignKey("OSId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Logins") + .HasForeignKey("HostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("gswi.Model.User", "User") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Users") + .HasForeignKey("DomainId"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Users") + .HasForeignKey("HostId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/gswi.Data/Migrations/20210213133159_2021_03_10.cs b/gswi.Data/Migrations/20210213133159_2021_03_10.cs new file mode 100644 index 0000000..7098940 --- /dev/null +++ b/gswi.Data/Migrations/20210213133159_2021_03_10.cs @@ -0,0 +1,249 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace gswi.Data.Migrations +{ + public partial class _2021_03_10 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AuthUsers", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + FirstName = table.Column(nullable: true), + LastName = table.Column(nullable: true), + EMail = table.Column(nullable: true), + Password = table.Column(nullable: true), + RefreshToken = table.Column(nullable: true), + ConfirmationId = table.Column(nullable: true), + ForgotPasswordId = table.Column(nullable: true), + RefreshTokenExpiryTime = table.Column(nullable: false), + CreatedOn = table.Column(nullable: false), + LastModifiedOn = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Domains", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + NotifyWhenLogin = table.Column(nullable: false), + CreatedOn = table.Column(nullable: false), + LastModifiedOn = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Domains", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OperatingSystems", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + CreatedOn = table.Column(nullable: false), + LastModifiedOn = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OperatingSystems", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Hosts", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + IPAddress = table.Column(maxLength: 1400, nullable: true), + NotifyWhenLogin = table.Column(nullable: false), + DomainId = table.Column(nullable: true), + OSId = table.Column(nullable: false), + CreatedOn = table.Column(nullable: false), + LastModifiedOn = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Hosts", x => x.Id); + table.ForeignKey( + name: "FK_Hosts_Domains_DomainId", + column: x => x.DomainId, + principalTable: "Domains", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Hosts_OperatingSystems_OSId", + column: x => x.OSId, + principalTable: "OperatingSystems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + NotifyWhenLogin = table.Column(nullable: false), + CreatedOn = table.Column(nullable: false), + LastModifiedOn = table.Column(nullable: false), + Discriminator = table.Column(nullable: false), + DomainId = table.Column(nullable: true), + HostId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_Domains_DomainId", + column: x => x.DomainId, + principalTable: "Domains", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Users_Hosts_HostId", + column: x => x.HostId, + principalTable: "Hosts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Logins", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Time = table.Column(nullable: false), + HostId = table.Column(nullable: false), + UserId = table.Column(nullable: false), + CreatedOn = table.Column(nullable: false), + LastModifiedOn = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Logins", x => x.Id); + table.ForeignKey( + name: "FK_Logins_Hosts_HostId", + column: x => x.HostId, + principalTable: "Hosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Logins_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuthUsers_ConfirmationId", + table: "AuthUsers", + column: "ConfirmationId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AuthUsers_EMail", + table: "AuthUsers", + column: "EMail", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AuthUsers_ForgotPasswordId", + table: "AuthUsers", + column: "ForgotPasswordId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Hosts_DomainId", + table: "Hosts", + column: "DomainId"); + + migrationBuilder.CreateIndex( + name: "IX_Hosts_OSId", + table: "Hosts", + column: "OSId"); + + migrationBuilder.CreateIndex( + name: "IX_Hosts_DomainId_Name", + table: "Hosts", + columns: new[] { "DomainId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Hosts_IPAddress_Name", + table: "Hosts", + columns: new[] { "IPAddress", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Logins_HostId", + table: "Logins", + column: "HostId"); + + migrationBuilder.CreateIndex( + name: "IX_Logins_UserId", + table: "Logins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_OperatingSystems_Name", + table: "OperatingSystems", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_DomainId", + table: "Users", + column: "DomainId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_HostId", + table: "Users", + column: "HostId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_Discriminator_Name", + table: "Users", + columns: new[] { "Discriminator", "Name" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuthUsers"); + + migrationBuilder.DropTable( + name: "Logins"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "Hosts"); + + migrationBuilder.DropTable( + name: "Domains"); + + migrationBuilder.DropTable( + name: "OperatingSystems"); + } + } +} diff --git a/gswi.Data/Migrations/20210217201310_2021_03_13.Designer.cs b/gswi.Data/Migrations/20210217201310_2021_03_13.Designer.cs new file mode 100644 index 0000000..5bd1e14 --- /dev/null +++ b/gswi.Data/Migrations/20210217201310_2021_03_13.Designer.cs @@ -0,0 +1,294 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using gswi.Data; + +namespace gswi.Data.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210217201310_2021_03_13")] + partial class _2021_03_13 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("gswi.Model.AuthUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AuthRole") + .HasColumnType("int"); + + b.Property("ConfirmationId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("EMail") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("FirstName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("ForgotPasswordId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Password") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshToken") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ConfirmationId") + .IsUnique(); + + b.HasIndex("EMail") + .IsUnique(); + + b.HasIndex("ForgotPasswordId") + .IsUnique(); + + b.ToTable("AuthUsers"); + }); + + modelBuilder.Entity("gswi.Model.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.Property("IPAddress") + .HasColumnType("varbinary(1400)") + .HasMaxLength(1400); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.Property("OSId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DomainId"); + + b.HasIndex("OSId"); + + b.HasIndex("DomainId", "Name") + .IsUnique(); + + b.HasIndex("IPAddress", "Name") + .IsUnique(); + + b.ToTable("Hosts"); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Time") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("HostId"); + + b.HasIndex("UserId"); + + b.ToTable("Logins"); + }); + + modelBuilder.Entity("gswi.Model.OperatingSystem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OperatingSystems"); + }); + + modelBuilder.Entity("gswi.Model.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Discriminator", "Name") + .IsUnique(); + + b.ToTable("Users"); + + b.HasDiscriminator("Discriminator").HasValue("User"); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.HasIndex("DomainId"); + + b.HasDiscriminator().HasValue("DomainUser"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.HasIndex("HostId"); + + b.HasDiscriminator().HasValue("HostUser"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Hosts") + .HasForeignKey("DomainId"); + + b.HasOne("gswi.Model.OperatingSystem", "OS") + .WithMany() + .HasForeignKey("OSId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Logins") + .HasForeignKey("HostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("gswi.Model.User", "User") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Users") + .HasForeignKey("DomainId"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Users") + .HasForeignKey("HostId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/gswi.Data/Migrations/20210217201310_2021_03_13.cs b/gswi.Data/Migrations/20210217201310_2021_03_13.cs new file mode 100644 index 0000000..ed5e4f3 --- /dev/null +++ b/gswi.Data/Migrations/20210217201310_2021_03_13.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace gswi.Data.Migrations +{ + public partial class _2021_03_13 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AuthRole", + table: "AuthUsers", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AuthRole", + table: "AuthUsers"); + } + } +} diff --git a/gswi.Data/Migrations/20210220140309_2021_03_14.Designer.cs b/gswi.Data/Migrations/20210220140309_2021_03_14.Designer.cs new file mode 100644 index 0000000..ac0dfdb --- /dev/null +++ b/gswi.Data/Migrations/20210220140309_2021_03_14.Designer.cs @@ -0,0 +1,291 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using gswi.Data; + +namespace gswi.Data.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210220140309_2021_03_14")] + partial class _2021_03_14 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("gswi.Model.AuthUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AuthRole") + .HasColumnType("int"); + + b.Property("ConfirmationId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("EMail") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("FirstName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("ForgotPasswordId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Password") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshToken") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ConfirmationId") + .IsUnique(); + + b.HasIndex("EMail") + .IsUnique(); + + b.HasIndex("ForgotPasswordId") + .IsUnique(); + + b.ToTable("AuthUsers"); + }); + + modelBuilder.Entity("gswi.Model.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.Property("IPAddress") + .HasColumnType("varbinary(1400)") + .HasMaxLength(1400); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.Property("OSId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DomainId"); + + b.HasIndex("OSId"); + + b.HasIndex("DomainId", "Name") + .IsUnique(); + + b.HasIndex("IPAddress", "Name") + .IsUnique(); + + b.ToTable("Hosts"); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Time") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("HostId"); + + b.HasIndex("UserId"); + + b.ToTable("Logins"); + }); + + modelBuilder.Entity("gswi.Model.OperatingSystem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OperatingSystems"); + }); + + modelBuilder.Entity("gswi.Model.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasDiscriminator("Discriminator").HasValue("User"); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.HasIndex("DomainId"); + + b.HasDiscriminator().HasValue("DomainUser"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.HasIndex("HostId"); + + b.HasDiscriminator().HasValue("HostUser"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Hosts") + .HasForeignKey("DomainId"); + + b.HasOne("gswi.Model.OperatingSystem", "OS") + .WithMany() + .HasForeignKey("OSId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Logins") + .HasForeignKey("HostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("gswi.Model.User", "User") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Users") + .HasForeignKey("DomainId"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Users") + .HasForeignKey("HostId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/gswi.Data/Migrations/20210220140309_2021_03_14.cs b/gswi.Data/Migrations/20210220140309_2021_03_14.cs new file mode 100644 index 0000000..d08fbbc --- /dev/null +++ b/gswi.Data/Migrations/20210220140309_2021_03_14.cs @@ -0,0 +1,53 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace gswi.Data.Migrations +{ + public partial class _2021_03_14 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Users_Discriminator_Name", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "Users", + nullable: true, + oldClrType: typeof(string), + oldType: "varchar(255) CHARACTER SET utf8mb4", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Discriminator", + table: "Users", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(255) CHARACTER SET utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "Users", + type: "varchar(255) CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Discriminator", + table: "Users", + type: "varchar(255) CHARACTER SET utf8mb4", + nullable: false, + oldClrType: typeof(string)); + + migrationBuilder.CreateIndex( + name: "IX_Users_Discriminator_Name", + table: "Users", + columns: new[] { "Discriminator", "Name" }, + unique: true); + } + } +} diff --git a/gswi.Data/Migrations/DatabaseContextModelSnapshot.cs b/gswi.Data/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 0000000..2a36a5f --- /dev/null +++ b/gswi.Data/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,289 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using gswi.Data; + +namespace gswi.Data.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("gswi.Model.AuthUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AuthRole") + .HasColumnType("int"); + + b.Property("ConfirmationId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("EMail") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("FirstName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("ForgotPasswordId") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Password") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshToken") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ConfirmationId") + .IsUnique(); + + b.HasIndex("EMail") + .IsUnique(); + + b.HasIndex("ForgotPasswordId") + .IsUnique(); + + b.ToTable("AuthUsers"); + }); + + modelBuilder.Entity("gswi.Model.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.Property("IPAddress") + .HasColumnType("varbinary(1400)") + .HasMaxLength(1400); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.Property("OSId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DomainId"); + + b.HasIndex("OSId"); + + b.HasIndex("DomainId", "Name") + .IsUnique(); + + b.HasIndex("IPAddress", "Name") + .IsUnique(); + + b.ToTable("Hosts"); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Time") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("HostId"); + + b.HasIndex("UserId"); + + b.ToTable("Logins"); + }); + + modelBuilder.Entity("gswi.Model.OperatingSystem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OperatingSystems"); + }); + + modelBuilder.Entity("gswi.Model.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedOn") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("NotifyWhenLogin") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasDiscriminator("Discriminator").HasValue("User"); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("DomainId") + .HasColumnType("bigint"); + + b.HasIndex("DomainId"); + + b.HasDiscriminator().HasValue("DomainUser"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasBaseType("gswi.Model.User"); + + b.Property("HostId") + .HasColumnType("bigint"); + + b.HasIndex("HostId"); + + b.HasDiscriminator().HasValue("HostUser"); + }); + + modelBuilder.Entity("gswi.Model.Host", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Hosts") + .HasForeignKey("DomainId"); + + b.HasOne("gswi.Model.OperatingSystem", "OS") + .WithMany() + .HasForeignKey("OSId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.Login", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Logins") + .HasForeignKey("HostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("gswi.Model.User", "User") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("gswi.Model.DomainUser", b => + { + b.HasOne("gswi.Model.Domain", "Domain") + .WithMany("Users") + .HasForeignKey("DomainId"); + }); + + modelBuilder.Entity("gswi.Model.HostUser", b => + { + b.HasOne("gswi.Model.Host", "Host") + .WithMany("Users") + .HasForeignKey("HostId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/gswi.Data/Repositories/AuthUserRepositoryImpl.cs b/gswi.Data/Repositories/AuthUserRepositoryImpl.cs new file mode 100644 index 0000000..4c53cb6 --- /dev/null +++ b/gswi.Data/Repositories/AuthUserRepositoryImpl.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using gswi.Interface.Repositories; +using gswi.Model; +using gswi.Share.Common; + +namespace gswi.Data.Repositories +{ + public class AuthUserRepositoryImpl : IAuthUserRepository + { + private readonly DatabaseContext _databaseContext; + + private DbSet _authUsers { get { return _databaseContext.AuthUsers; } } + + public AuthUserRepositoryImpl(DatabaseContext databaseContext) + { + _databaseContext = databaseContext; + } + + public async Task> GetAllAuthUsersAsync() + { + return await _authUsers + .ToListAsync(); + } + + public async Task<(List, int totalCount)> GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion) + { + var query = _authUsers + .Select(u => new AuthUser + { + Id = u.Id, + FirstName = u.FirstName, + LastName = u.LastName, + EMail = u.EMail, + ConfirmationId = u.ConfirmationId, + AuthRole = u.AuthRole + }); + + // FirstName filter + if (!string.IsNullOrWhiteSpace(selectCriterion.FirstName)) + query = query.Where(u => u.FirstName.Contains(selectCriterion.FirstName) || u.FirstName == selectCriterion.FirstName); + + // LastName filter + if (!string.IsNullOrWhiteSpace(selectCriterion.LastName)) + query = query.Where(u => u.LastName.Contains(selectCriterion.LastName) || u.LastName == selectCriterion.LastName); + + // EMail filter + if (!string.IsNullOrWhiteSpace(selectCriterion.EMail)) + query = query.Where(u => u.EMail.Contains(selectCriterion.EMail) || u.EMail == selectCriterion.EMail); + + // AuthRole filter + if (selectCriterion.AuthRole != null) + query = query.Where(u => u.AuthRole == (AuthRoles)selectCriterion.AuthRole); + + // sort + if (!string.IsNullOrWhiteSpace(selectCriterion.SortColumn) && !string.IsNullOrWhiteSpace(selectCriterion.SortDirection)) + { + var critSortDirection = selectCriterion.SortDirection?.ToLower(); + if (critSortDirection == "desc" || critSortDirection == "descending") + query = query.OrderByMemberDescending(selectCriterion.SortColumn); + else + query = query.OrderByMember(selectCriterion.SortColumn); + } + + var skip = selectCriterion.PageSize * selectCriterion.PageIndex; + if (skip < 0) + skip = 0; + + var totalCount = await query.CountAsync(); + + var list = await query.Skip(skip).Take(selectCriterion.PageSize).AsNoTracking().ToListAsync(); + + return (list, totalCount); + } + + public async Task GetAuthUserByEMailAsync(string email) + { + return await _authUsers + .Where(au => au.EMail == email) + .FirstAsync(); + } + + public async Task FindAuthUserByEMailAsync(string email) + { + return await _authUsers + .Where(au => au.EMail == email) + .FirstOrDefaultAsync(); + } + + public async Task FindAuthUserByEMailConfirmationIdAsync(string id) + { + return await _authUsers + .Where(au => au.ConfirmationId == id) + .FirstOrDefaultAsync(); + } + + public async Task FindAuthUserByEMailForgotPasswordIdAsync(string id) + { + return await _authUsers + .Where(au => au.ForgotPasswordId == id) + .FirstOrDefaultAsync(); + } + + public void AddAuthUser(AuthUser user) + { + _authUsers.Add(user); + } + + public async Task DeleteAuthUserByEMailAsync(string email) + { + var au = await _authUsers + .Where(au => au.EMail == email) + .FirstAsync(); + + _authUsers.Remove(au); + } + + public void DeleteAuthUser(AuthUser user) + { + _authUsers.Remove(user); + } + } +} \ No newline at end of file diff --git a/gswi.Data/gswi.Data.csproj b/gswi.Data/gswi.Data.csproj new file mode 100644 index 0000000..f097d8f --- /dev/null +++ b/gswi.Data/gswi.Data.csproj @@ -0,0 +1,23 @@ + + + net6.0 + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/gswi.Interface/Repositories/IAuthUserRepository.cs b/gswi.Interface/Repositories/IAuthUserRepository.cs new file mode 100644 index 0000000..f1588ad --- /dev/null +++ b/gswi.Interface/Repositories/IAuthUserRepository.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using gswi.Model; +using gswi.Share.Common; + +namespace gswi.Interface.Repositories +{ + public interface IAuthUserRepository + { + Task> GetAllAuthUsersAsync(); + Task<(List, int totalCount)> GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion); + Task GetAuthUserByEMailAsync(string email); + Task FindAuthUserByEMailAsync(string email); + Task FindAuthUserByEMailConfirmationIdAsync(string id); + Task FindAuthUserByEMailForgotPasswordIdAsync(string id); + void AddAuthUser(AuthUser user); + Task DeleteAuthUserByEMailAsync(string email); + void DeleteAuthUser(AuthUser user); + } +} \ No newline at end of file diff --git a/gswi.Interface/Repositories/IUnitOfWork.cs b/gswi.Interface/Repositories/IUnitOfWork.cs new file mode 100644 index 0000000..db97df7 --- /dev/null +++ b/gswi.Interface/Repositories/IUnitOfWork.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace gswi.Interface.Repositories { + public interface IUnitOfWork : IDisposable { + int SaveChanges(); + Task SaveChangesAsync(); + } +} diff --git a/gswi.Interface/Services/IAuthService.cs b/gswi.Interface/Services/IAuthService.cs new file mode 100644 index 0000000..0172a14 --- /dev/null +++ b/gswi.Interface/Services/IAuthService.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Threading.Tasks; +using gswi.Model.DTOs; +using gswi.Share.Common; + +namespace gswi.Interface.Services +{ + public interface IAuthService + { + Task> GetAllAuthUsersAsync(); + Task GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion); + Task GetAuthUserByEMailAsync(string email); + Task FindAuthUserByEMailAsync(string email); + Task AddAuthUserAsync(AuthUserDTO userDTO); + Task ConfirmEMail(string id); + Task Login(AuthUserDTO userDTO); + Task ForgotPassword(string email); + Task ConfirmForgotPassword(string id); + Task ResetPassword(ResetPasswordDTO rpDTO); + Task UpdateUser(UpdateUserDTO updateUserDTO); + Task UpdateUserAsAdmin(AdminUpdateUserDTO updateUserDTO); + Task Refresh(TokenDTO tokenDTO); + Task Revoke(TokenDTO tokenDTO); + Task DeleteAuthUserByEMailAsync(string email); + Task DeleteAuthUserAsync(AuthUserDTO userDTO); + } +} \ No newline at end of file diff --git a/gswi.Interface/gswi.Interface.csproj b/gswi.Interface/gswi.Interface.csproj new file mode 100644 index 0000000..61b3a45 --- /dev/null +++ b/gswi.Interface/gswi.Interface.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + net6.0 + + \ No newline at end of file diff --git a/gswi.Model/AuthRoles.cs b/gswi.Model/AuthRoles.cs new file mode 100644 index 0000000..0066f3f --- /dev/null +++ b/gswi.Model/AuthRoles.cs @@ -0,0 +1,10 @@ +using System; + +namespace gswi.Model +{ + public enum AuthRoles + { + Normal = 0, + Admin = 1 + } +} diff --git a/gswi.Model/AuthUser.cs b/gswi.Model/AuthUser.cs new file mode 100644 index 0000000..8487b27 --- /dev/null +++ b/gswi.Model/AuthUser.cs @@ -0,0 +1,34 @@ +using System; +using gswi.Model.DTOs; + +namespace gswi.Model +{ + public class AuthUser : IAutoGenerateDateFields + { + public long Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string EMail { get; set; } + public string Password { get; set; } + public AuthRoles AuthRole { get; set; } + public string RefreshToken { get; set; } + public string ConfirmationId { get; set; } + public string ForgotPasswordId { get; set; } + public DateTime RefreshTokenExpiryTime { get; set; } + public DateTimeOffset CreatedOn { get; set; } + public DateTimeOffset LastModifiedOn { get; set; } + + public AuthUserDTO ToAuthUserDTO() + { + return new AuthUserDTO + { + Id = this.Id, + FirstName = this.FirstName, + LastName = this.LastName, + EMail = this.EMail, + IsConfirmed = this.ConfirmationId == null, + AuthRole = this.AuthRole + }; + } + } +} \ No newline at end of file diff --git a/gswi.Model/DTOs/AdminUpdateUserDTO.cs b/gswi.Model/DTOs/AdminUpdateUserDTO.cs new file mode 100644 index 0000000..84809f7 --- /dev/null +++ b/gswi.Model/DTOs/AdminUpdateUserDTO.cs @@ -0,0 +1,11 @@ +using System; + +namespace gswi.Model.DTOs +{ + public class AdminUpdateUserDTO + { + public AuthUserDTO AuthUserDTO { get; set; } + public AuthUserDTO NewAuthUserDTO { get; set; } + public bool ChangePassword { get; set; } + } +} diff --git a/gswi.Model/DTOs/ApiVersionDTO.cs b/gswi.Model/DTOs/ApiVersionDTO.cs new file mode 100644 index 0000000..eb8bcd9 --- /dev/null +++ b/gswi.Model/DTOs/ApiVersionDTO.cs @@ -0,0 +1,11 @@ +using System; + +namespace gswi.Model.DTOs +{ + public class ApiVersionDTO + { + public string Major { get; set; } + public string Minor { get; set; } + public string Micro { get; set; } + } +} diff --git a/gswi.Model/DTOs/AuthUserDTO.cs b/gswi.Model/DTOs/AuthUserDTO.cs new file mode 100644 index 0000000..b1663fe --- /dev/null +++ b/gswi.Model/DTOs/AuthUserDTO.cs @@ -0,0 +1,27 @@ +namespace gswi.Model.DTOs +{ + public class AuthUserDTO + { + public long Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string EMail { get; set; } + public string Password { get; set; } + public bool IsConfirmed { get; set; } + public AuthRoles AuthRole { get; set; } + + public AuthUser ToAuthUser() + { + return new AuthUser + { + Id = this.Id, + FirstName = this.FirstName, + LastName = this.LastName, + EMail = this.EMail, + Password = this.Password, + AuthRole = this.AuthRole + }; + } + + } +} \ No newline at end of file diff --git a/gswi.Model/DTOs/EMailStringDTO.cs b/gswi.Model/DTOs/EMailStringDTO.cs new file mode 100644 index 0000000..be25112 --- /dev/null +++ b/gswi.Model/DTOs/EMailStringDTO.cs @@ -0,0 +1,9 @@ +using System; + +namespace gswi.Model.DTOs +{ + public class EMailStringDTO + { + public string EMail { get; set; } + } +} diff --git a/gswi.Model/DTOs/GetFilteredAuthUsersResultDTO.cs b/gswi.Model/DTOs/GetFilteredAuthUsersResultDTO.cs new file mode 100644 index 0000000..d477c77 --- /dev/null +++ b/gswi.Model/DTOs/GetFilteredAuthUsersResultDTO.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace gswi.Model.DTOs +{ + public class GetFilteredAuthUsersResultDTO + { + public List Users { get; set; } + public int TotalCount { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Model/DTOs/ResetPasswordDTO.cs b/gswi.Model/DTOs/ResetPasswordDTO.cs new file mode 100644 index 0000000..1fc9eed --- /dev/null +++ b/gswi.Model/DTOs/ResetPasswordDTO.cs @@ -0,0 +1,10 @@ +using System; + +namespace gswi.Model.DTOs +{ + public class ResetPasswordDTO + { + public string Id { get; set; } + public string Password { get; set; } + } +} diff --git a/gswi.Model/DTOs/SettingsDTO.cs b/gswi.Model/DTOs/SettingsDTO.cs new file mode 100644 index 0000000..11c006e --- /dev/null +++ b/gswi.Model/DTOs/SettingsDTO.cs @@ -0,0 +1,22 @@ +using System; + +namespace gswi.Model.DTOs +{ + public class SettingsDTO + { + public string WebVersion { get; set; } + public string ApiVersion { get; set; } + public string ConfigPath { get; set; } + public string WebBaseURL { get; set; } + public string ApiBaseURL { get; set; } + + public int TokenExpireTime { get; set; } + public int RefreshTokenExpireTime { get; set; } + + public string MailUser { get; set; } + public int MailPort { get; set; } + public string MailHost { get; set; } + public string MailTransceiver { get; set; } + public string MailTransceiverAddress { get; set; } + } +} diff --git a/gswi.Model/DTOs/TokenDTO.cs b/gswi.Model/DTOs/TokenDTO.cs new file mode 100644 index 0000000..29a9f38 --- /dev/null +++ b/gswi.Model/DTOs/TokenDTO.cs @@ -0,0 +1,8 @@ +namespace gswi.Model.DTOs +{ + public class TokenDTO + { + public string Token { get; set; } + public string RefreshToken { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Model/DTOs/UpdateUserDTO.cs b/gswi.Model/DTOs/UpdateUserDTO.cs new file mode 100644 index 0000000..cabf06b --- /dev/null +++ b/gswi.Model/DTOs/UpdateUserDTO.cs @@ -0,0 +1,10 @@ +using System; + +namespace gswi.Model.DTOs +{ + public class UpdateUserDTO + { + public AuthUserDTO AuthUserDTO { get; set; } + public AuthUserDTO NewAuthUserDTO { get; set; } + } +} diff --git a/gswi.Model/DTOs/UserDTO.cs b/gswi.Model/DTOs/UserDTO.cs new file mode 100644 index 0000000..f8b2cf4 --- /dev/null +++ b/gswi.Model/DTOs/UserDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace gswi.Model.DTOs +{ + public abstract class UserDTO + { + public long Id { get; set; } + public string Name { get; set; } + public bool NotifyWhenLogin { get; set; } + } +} diff --git a/gswi.Model/Filter/ErrorDTO.cs b/gswi.Model/Filter/ErrorDTO.cs new file mode 100644 index 0000000..32b3fe9 --- /dev/null +++ b/gswi.Model/Filter/ErrorDTO.cs @@ -0,0 +1,21 @@ +namespace gswi.Model.Filters +{ + public enum ErrorType { + ValidationError, + Exception + } + + public class ErrorDto { + public ServiceErrorCode ErrorCode { get; set; } + public string Message { get; set; } + + public ErrorDto(string message) + : this(ServiceErrorCode.Unknown, message) { + } + + public ErrorDto(ServiceErrorCode errorCode, string message) { + Message = message; + ErrorCode = errorCode; + } + } +} \ No newline at end of file diff --git a/gswi.Model/Filter/ServiceErrorCode.cs b/gswi.Model/Filter/ServiceErrorCode.cs new file mode 100644 index 0000000..204b8d7 --- /dev/null +++ b/gswi.Model/Filter/ServiceErrorCode.cs @@ -0,0 +1,21 @@ +using System.Threading; +namespace gswi.Model.Filters +{ + public enum ServiceErrorCode + { + Unknown = 0, + + InvalidDependencies = 1, + InvalidData = 2, + NotFound = 3, + DataAlreadyExists = 4, + UnableToAdd = 5, + UnableToDelete = 6, + + InvalidUser = 7, + + ConnectionFailed = 8, + Timeout = 9, + MailError = 10 + } +} \ No newline at end of file diff --git a/gswi.Model/Filter/ServiceException.cs b/gswi.Model/Filter/ServiceException.cs new file mode 100644 index 0000000..9b79d72 --- /dev/null +++ b/gswi.Model/Filter/ServiceException.cs @@ -0,0 +1,23 @@ +using System; + +namespace gswi.Model.Filters +{ + public class ServiceException: Exception { + public ServiceErrorCode ErrorCode { get; set; } + public string ErrorMessage { get; set; } + public string Nameof { get; set; } + + + public ServiceException(ServiceErrorCode errorCode, string errorMessage) { + ErrorCode = errorCode; + ErrorMessage = errorMessage; + } + + public string GetMessage() { + return ErrorMessage; + } + public string GetDetailedMessage() { + return $"{nameof(ServiceException)} - ErrorCode: {ErrorCode} - ErrorMessage: {ErrorMessage}"; + } + } +} \ No newline at end of file diff --git a/gswi.Model/IAutoGenerateDateFields.cs b/gswi.Model/IAutoGenerateDateFields.cs new file mode 100644 index 0000000..fcddcb1 --- /dev/null +++ b/gswi.Model/IAutoGenerateDateFields.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace gswi.Model { + public interface IAutoGenerateDateFields { + DateTimeOffset CreatedOn { get; set; } + DateTimeOffset LastModifiedOn { get; set; } + } +} diff --git a/gswi.Model/gswi.Model.csproj b/gswi.Model/gswi.Model.csproj new file mode 100644 index 0000000..7597ca1 --- /dev/null +++ b/gswi.Model/gswi.Model.csproj @@ -0,0 +1,8 @@ + + + net6.0 + + + + + \ No newline at end of file diff --git a/gswi.SMTP.Interface/ISMTPClient.cs b/gswi.SMTP.Interface/ISMTPClient.cs new file mode 100644 index 0000000..c561634 --- /dev/null +++ b/gswi.SMTP.Interface/ISMTPClient.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using gswi.SMTP.Model; + +namespace gswi.SMTP.Interface +{ + public interface ISMTPClient + { + Task SendEmailAsync(EMail email); + } +} \ No newline at end of file diff --git a/gswi.SMTP.Interface/gswi.SMTP.Interface.csproj b/gswi.SMTP.Interface/gswi.SMTP.Interface.csproj new file mode 100644 index 0000000..a41e17b --- /dev/null +++ b/gswi.SMTP.Interface/gswi.SMTP.Interface.csproj @@ -0,0 +1,11 @@ + + + + + + + + net6.0 + + + diff --git a/gswi.SMTP.Model/EMail.cs b/gswi.SMTP.Model/EMail.cs new file mode 100644 index 0000000..dd78a8d --- /dev/null +++ b/gswi.SMTP.Model/EMail.cs @@ -0,0 +1,11 @@ +using System; + +namespace gswi.SMTP.Model +{ + public class EMail + { + public string Receiver { get; set; } + public string Subject { get; set; } + public string Message { get; set; } + } +} diff --git a/gswi.SMTP.Model/gswi.SMTP.Model.csproj b/gswi.SMTP.Model/gswi.SMTP.Model.csproj new file mode 100644 index 0000000..dbc1517 --- /dev/null +++ b/gswi.SMTP.Model/gswi.SMTP.Model.csproj @@ -0,0 +1,7 @@ + + + + net6.0 + + + diff --git a/gswi.SMTP.Service/SMTPClientImpl.cs b/gswi.SMTP.Service/SMTPClientImpl.cs new file mode 100644 index 0000000..688e95b --- /dev/null +++ b/gswi.SMTP.Service/SMTPClientImpl.cs @@ -0,0 +1,80 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using MailKit.Net.Smtp; +using MailKit.Security; +using MimeKit; +using MimeKit.Text; +using NLog; +using gswi.Configuration; +using gswi.Model.Filters; +using gswi.SMTP.Interface; +using gswi.SMTP.Model; + +namespace gswi.SMTP.Service +{ + public class SMTPClientImpl : ISMTPClient + { + private readonly EMailSettings _emailSettings; + private static Logger _logger = LogManager.GetCurrentClassLogger(); + + public SMTPClientImpl(EMailSettings emailSettings) + { + this._emailSettings = emailSettings; + } + + public async Task SendEmailAsync(EMail email) + { + var emailMessage = new MimeMessage(); + + emailMessage.From.Add(new MailboxAddress(_emailSettings.FromName, _emailSettings.FromAddress)); + emailMessage.To.Add(new MailboxAddress("", email.Receiver)); + emailMessage.Subject = email.Subject; + emailMessage.Body = new TextPart(TextFormat.Html) { Text = $"{email.Message}

Dies ist eine Automatische E-Mail.
Gesendet von Login counter @ {System.Net.Dns.GetHostName()}" }; + + using (var client = new SmtpClient()) + { + client.Timeout = 30000; + try + { + await client.ConnectAsync(_emailSettings.MailServerAddress, _emailSettings.MailServerPort, SecureSocketOptions.Auto).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.Error(ex); + throw new ServiceException(ServiceErrorCode.ConnectionFailed, "Connection to mail server failed"); + } + + try + { + await client.AuthenticateAsync(new NetworkCredential(_emailSettings.Username, _emailSettings.Credentials)); + } + catch (Exception ex) + { + _logger.Error(ex); + throw new ServiceException(ServiceErrorCode.InvalidUser, "Authentification to mail server failed"); + } + + try + { + await client.SendAsync(emailMessage).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.Error(ex); + throw new ServiceException(ServiceErrorCode.MailError, "Unable to send email"); + } + + try + { + await client.DisconnectAsync(true).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.Error(ex); + throw new ServiceException(ServiceErrorCode.ConnectionFailed, "Unable to disconnect from mail server"); + } + } + } + } +} \ No newline at end of file diff --git a/gswi.SMTP.Service/gswi.SMTP.Service.csproj b/gswi.SMTP.Service/gswi.SMTP.Service.csproj new file mode 100644 index 0000000..5b019a9 --- /dev/null +++ b/gswi.SMTP.Service/gswi.SMTP.Service.csproj @@ -0,0 +1,15 @@ + + + + + + + + + + + + + net6.0 + + \ No newline at end of file diff --git a/gswi.Service/AuthServiceImpl.cs b/gswi.Service/AuthServiceImpl.cs new file mode 100644 index 0000000..f3b3aca --- /dev/null +++ b/gswi.Service/AuthServiceImpl.cs @@ -0,0 +1,604 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Tokens; +using NLog; +using gswi.Configuration; +using gswi.Interface.Repositories; +using gswi.Interface.Services; +using gswi.Model; +using gswi.Model.DTOs; +using gswi.Model.Filters; +using gswi.Share.Common; +using gswi.SMTP.Interface; +using gswi.SMTP.Model; + +namespace gswi.Service +{ + public class AuthServiceImpl : IAuthService + { + private readonly IAuthUserRepository _authUserRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly AuthentificationSettings _authSettings; + private readonly ISMTPClient _smtpClient; + private readonly FrontendSettings _frontendSettings; + private static Logger _logger = LogManager.GetCurrentClassLogger(); + private static Random random = new Random(); + + public AuthServiceImpl( + IAuthUserRepository authUserRepository, + IUnitOfWork unitOfWork, + AuthentificationSettings authSettings, + ISMTPClient smtpClient, + FrontendSettings frontendSettings + ) + { + _unitOfWork = unitOfWork; + _authUserRepository = authUserRepository; + _authSettings = authSettings; + _smtpClient = smtpClient; + _frontendSettings = frontendSettings; + } + + private static string _randomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + + private string _generateToken(IEnumerable claims) + { + var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authSettings.SecretKey)); + var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); + + var tokenOptions = new JwtSecurityToken( + issuer: _authSettings.Issuer, + audience: _authSettings.Audience, + claims: claims, + expires: DateTime.Now.AddMinutes(_authSettings.TokenExpireTime), + signingCredentials: signingCredentials + ); + + return new JwtSecurityTokenHandler().WriteToken(tokenOptions); + } + + private string _generateRefreshToken() + { + var randomNumber = new byte[32]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomNumber); + return Convert.ToBase64String(randomNumber); + } + } + + private async Task _createAndSaveRefreshToken(AuthUser user) + { + var refreshToken = this._generateRefreshToken(); + + user.RefreshToken = refreshToken; + user.RefreshTokenExpiryTime = DateTime.Now.AddDays(_authSettings.RefreshTokenExpireTime); + + await _unitOfWork.SaveChangesAsync(); + + return refreshToken; + } + + private ClaimsPrincipal _getPrincipalFromExpiredToken(string token) + { + var tokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case + ValidateIssuer = false, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._authSettings.SecretKey)), + ValidateLifetime = false //here we are saying that we don't care about the token's expiration date + }; + var tokenHandler = new JwtSecurityTokenHandler(); + SecurityToken securityToken; + var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken); + var jwtSecurityToken = securityToken as JwtSecurityToken; + if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) + throw new SecurityTokenException("Invalid token"); + return principal; + } + + private async Task _createAndSaveConfirmationId(AuthUser user) + { + bool end = false; + while (!end) + { + string id = _randomString(16); + var userFromDb = await _authUserRepository.FindAuthUserByEMailConfirmationIdAsync(id); + if (userFromDb is null) + { + end = true; + user.ConfirmationId = id; + } + } + } + + private async Task _createAndSaveForgotPasswordId(AuthUser user) + { + bool end = false; + while (!end) + { + string id = _randomString(16); + var userFromDb = await _authUserRepository.FindAuthUserByEMailForgotPasswordIdAsync(id); + if (userFromDb is null) + { + end = true; + user.ForgotPasswordId = id; + } + } + } + + private async Task _sendConfirmationIdToUser(AuthUser user) + { + string url = _frontendSettings.URL.EndsWith("/") ? _frontendSettings.URL : $"{_frontendSettings}/"; + await _smtpClient.SendEmailAsync(new EMail() + { + Receiver = user.EMail, + Subject = $"E-Mail für {user.FirstName} {user.LastName} bestätigen", + Message = $"{url}auth/register/{user.ConfirmationId}" + }); + } + + private async Task _sendForgotPasswordIdToUser(AuthUser user) + { + string url = _frontendSettings.URL.EndsWith("/") ? _frontendSettings.URL : $"{_frontendSettings}/"; + await _smtpClient.SendEmailAsync(new EMail() + { + Receiver = user.EMail, + Subject = $"Passwort für {user.FirstName} {user.LastName} zurücksetzen", + Message = $"{url}auth/forgot-password/{user.ForgotPasswordId}" + }); + } + + public async Task> GetAllAuthUsersAsync() + { + var authUserDTOs = new List(); + var authUsers = await _authUserRepository.GetAllAuthUsersAsync(); + authUsers.ForEach(authUser => + { + authUserDTOs.Add(authUser.ToAuthUserDTO()); + }); + return authUserDTOs; + + } + + public async Task GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion) { + (var users, var totalCount) = await _authUserRepository.GetFilteredAuthUsersAsync(selectCriterion); + var result = new List(); + + users.ForEach(user => { + result.Add(user.ToAuthUserDTO()); + }); + + return new GetFilteredAuthUsersResultDTO() { + Users = result, + TotalCount = totalCount + }; + } + + public async Task GetAuthUserByEMailAsync(string email) + { + try + { + var authUser = await _authUserRepository.GetAuthUserByEMailAsync(email); + return authUser.ToAuthUserDTO(); + } + catch (Exception e) + { + _logger.Error(e); + throw new ServiceException(ServiceErrorCode.InvalidData, $"AuthUser with email {email} not found"); + } + } + + public async Task FindAuthUserByEMailAsync(string email) + { + var authUser = await _authUserRepository.FindAuthUserByEMailAsync(email); + return authUser != null ? authUser.ToAuthUserDTO() : null; + } + + public async Task AddAuthUserAsync(AuthUserDTO authUserDTO) + { + var authUserDb = await _authUserRepository.FindAuthUserByEMailAsync(authUserDTO.EMail); + if (authUserDb != null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User already exists"); + } + + + authUserDTO.Password = ComputeHash(authUserDTO.Password, new SHA256CryptoServiceProvider()); + + var authUser = authUserDTO.ToAuthUser(); + + if (!IsValidEmail(authUser.EMail)) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Invalid E-Mail"); + } + try + { + + _authUserRepository.AddAuthUser(authUser); + await _createAndSaveConfirmationId(authUser); + await _sendConfirmationIdToUser(authUser); + await _unitOfWork.SaveChangesAsync(); + _logger.Info($"Added authUser with email: {authUser.EMail}"); + return authUser.Id; + } + catch (Exception e) + { + _logger.Error(e); + throw new ServiceException(ServiceErrorCode.UnableToAdd, $"Cannot add authUser {authUserDTO.EMail}"); + } + } + + public async Task ConfirmEMail(string id) + { + var user = await _authUserRepository.FindAuthUserByEMailConfirmationIdAsync(id); + if (user.ConfirmationId == id) + { + user.ConfirmationId = null; + await _unitOfWork.SaveChangesAsync(); + return true; + } + return false; + } + + public async Task Login(AuthUserDTO userDTO) + { + if (userDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"User is empty"); + } + + var userFromDb = await _authUserRepository.FindAuthUserByEMailAsync(userDTO.EMail); + if (userFromDb == null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found"); + } + + if (userFromDb.ConfirmationId != null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed"); + } + + userDTO.Password = ComputeHash(userDTO.Password, new SHA256CryptoServiceProvider()); + if (userFromDb.Password != userDTO.Password) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "Wrong password"); + } + + var tokenString = this._generateToken(new List() { + new Claim(ClaimTypes.Name, userDTO.EMail), + new Claim(ClaimTypes.Role, userFromDb.AuthRole.ToString()) + }); + + var refreshString = await this._createAndSaveRefreshToken(userFromDb); + + if (userFromDb.ForgotPasswordId != null) + { + userFromDb.ForgotPasswordId = null; + await _unitOfWork.SaveChangesAsync(); + } + + return new TokenDTO + { + Token = tokenString, + RefreshToken = refreshString + }; + } + + public async Task ForgotPassword(string email) + { + var user = await _authUserRepository.FindAuthUserByEMailAsync(email); + if (user is null) + { + return; + } + await _createAndSaveForgotPasswordId(user); + await _sendForgotPasswordIdToUser(user); + await _unitOfWork.SaveChangesAsync(); + } + + public async Task ConfirmForgotPassword(string id) + { + var user = await _authUserRepository.FindAuthUserByEMailForgotPasswordIdAsync(id); + return new EMailStringDTO() + { + EMail = user.EMail + }; + } + + public async Task ResetPassword(ResetPasswordDTO rpDTO) + { + var user = await _authUserRepository.FindAuthUserByEMailForgotPasswordIdAsync(rpDTO.Id); + if (user == null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found"); + } + + if (user.ConfirmationId != null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed"); + } + + if (rpDTO.Password == null || rpDTO.Password == "") + { + throw new ServiceException(ServiceErrorCode.InvalidData, "Password is empty"); + } + + rpDTO.Password = ComputeHash(rpDTO.Password, new SHA256CryptoServiceProvider()); + user.Password = rpDTO.Password; + await _unitOfWork.SaveChangesAsync(); + } + + public async Task UpdateUser(UpdateUserDTO updateUserDTO) + { + if (updateUserDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"User is empty"); + } + + if (updateUserDTO.AuthUserDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Existing user is empty"); + } + + if (updateUserDTO.NewAuthUserDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"New user is empty"); + } + + if (!IsValidEmail(updateUserDTO.AuthUserDTO.EMail) || !IsValidEmail(updateUserDTO.NewAuthUserDTO.EMail)) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Invalid E-Mail"); + } + + var user = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.AuthUserDTO.EMail); + if (user == null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found"); + } + + if (user.ConfirmationId != null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed"); + } + + // update first name + if (updateUserDTO.NewAuthUserDTO.FirstName != null && updateUserDTO.AuthUserDTO.FirstName != updateUserDTO.NewAuthUserDTO.FirstName) + { + user.FirstName = updateUserDTO.NewAuthUserDTO.FirstName; + } + + // update last name + if (updateUserDTO.NewAuthUserDTO.LastName != null && updateUserDTO.NewAuthUserDTO.LastName != "" && updateUserDTO.AuthUserDTO.LastName != updateUserDTO.NewAuthUserDTO.LastName) + { + user.LastName = updateUserDTO.NewAuthUserDTO.LastName; + } + + // update E-Mail + if (updateUserDTO.NewAuthUserDTO.EMail != null && updateUserDTO.NewAuthUserDTO.EMail != "" && updateUserDTO.AuthUserDTO.EMail != updateUserDTO.NewAuthUserDTO.EMail) + { + var userByNewEMail = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.NewAuthUserDTO.EMail); + if (userByNewEMail != null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User already exists"); + } + user.EMail = updateUserDTO.NewAuthUserDTO.EMail; + } + + bool isExistingPasswordSet = false; + bool isnewPasswordSet = false; + // hash passwords in DTOs + if (updateUserDTO.AuthUserDTO.Password != null && updateUserDTO.AuthUserDTO.Password != "") + { + isExistingPasswordSet = true; + updateUserDTO.AuthUserDTO.Password = ComputeHash(updateUserDTO.AuthUserDTO.Password, new SHA256CryptoServiceProvider()); + } + + if (updateUserDTO.AuthUserDTO.Password != user.Password) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "Wrong password"); + } + + if (updateUserDTO.NewAuthUserDTO.Password != null && updateUserDTO.NewAuthUserDTO.Password != "") + { + isnewPasswordSet = true; + updateUserDTO.NewAuthUserDTO.Password = ComputeHash(updateUserDTO.NewAuthUserDTO.Password, new SHA256CryptoServiceProvider()); + } + + // update password + if (isExistingPasswordSet && isnewPasswordSet && updateUserDTO.AuthUserDTO.Password != updateUserDTO.NewAuthUserDTO.Password) + { + user.Password = updateUserDTO.NewAuthUserDTO.Password; + } + + await _unitOfWork.SaveChangesAsync(); + } + + public async Task UpdateUserAsAdmin(AdminUpdateUserDTO updateUserDTO) + { + if (updateUserDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"User is empty"); + } + + if (updateUserDTO.AuthUserDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Existing user is empty"); + } + + if (updateUserDTO.NewAuthUserDTO == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"New user is empty"); + } + + if (!IsValidEmail(updateUserDTO.AuthUserDTO.EMail) || !IsValidEmail(updateUserDTO.NewAuthUserDTO.EMail)) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Invalid E-Mail"); + } + + var user = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.AuthUserDTO.EMail); + if (user == null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found"); + } + + if (user.ConfirmationId != null && updateUserDTO.NewAuthUserDTO.IsConfirmed) + { + user.ConfirmationId = null; + } + else if (user.ConfirmationId == null && !updateUserDTO.NewAuthUserDTO.IsConfirmed) + { + + await _createAndSaveConfirmationId(user); + } + // else + // { + // throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed"); + // } + + // update first name + if (updateUserDTO.NewAuthUserDTO.FirstName != null && updateUserDTO.AuthUserDTO.FirstName != updateUserDTO.NewAuthUserDTO.FirstName) + { + user.FirstName = updateUserDTO.NewAuthUserDTO.FirstName; + } + + // update last name + if (updateUserDTO.NewAuthUserDTO.LastName != null && updateUserDTO.NewAuthUserDTO.LastName != "" && updateUserDTO.AuthUserDTO.LastName != updateUserDTO.NewAuthUserDTO.LastName) + { + user.LastName = updateUserDTO.NewAuthUserDTO.LastName; + } + + // update E-Mail + if (updateUserDTO.NewAuthUserDTO.EMail != null && updateUserDTO.NewAuthUserDTO.EMail != "" && updateUserDTO.AuthUserDTO.EMail != updateUserDTO.NewAuthUserDTO.EMail) + { + var userByNewEMail = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.NewAuthUserDTO.EMail); + if (userByNewEMail != null) + { + throw new ServiceException(ServiceErrorCode.InvalidUser, "User already exists"); + } + user.EMail = updateUserDTO.NewAuthUserDTO.EMail; + } + + // update password + if (updateUserDTO.ChangePassword && updateUserDTO.AuthUserDTO.Password != updateUserDTO.NewAuthUserDTO.Password) + { + user.Password = ComputeHash(updateUserDTO.NewAuthUserDTO.Password, new SHA256CryptoServiceProvider()); + } + + // update role + if (user.AuthRole == updateUserDTO.AuthUserDTO.AuthRole && user.AuthRole != updateUserDTO.NewAuthUserDTO.AuthRole) + { + user.AuthRole = updateUserDTO.NewAuthUserDTO.AuthRole; + } + + await _unitOfWork.SaveChangesAsync(); + } + + public async Task Refresh(TokenDTO tokenDTO) + { + if (tokenDTO is null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is empty"); + } + + var principal = this._getPrincipalFromExpiredToken(tokenDTO.Token); + var email = principal.Identity.Name; + + var user = await this._authUserRepository.FindAuthUserByEMailAsync(email); + if (user == null || user.RefreshToken != tokenDTO.RefreshToken || user.RefreshTokenExpiryTime <= DateTime.Now) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is expired"); + } + + var newToken = this._generateToken(principal.Claims); + var newRefreshToken = await this._createAndSaveRefreshToken(user); + + return new TokenDTO() + { + Token = newToken, + RefreshToken = newRefreshToken + }; + } + + public async Task Revoke(TokenDTO tokenDTO) + { + if (tokenDTO == null || tokenDTO.Token == null || tokenDTO.RefreshToken == null) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is empty"); + }; + + var principal = this._getPrincipalFromExpiredToken(tokenDTO.Token); + var email = principal.Identity.Name; + + var user = await this._authUserRepository.FindAuthUserByEMailAsync(email); + if (user == null || user.RefreshToken != tokenDTO.RefreshToken || user.RefreshTokenExpiryTime <= DateTime.Now) + { + throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is expired"); + } + user.RefreshToken = null; + await _unitOfWork.SaveChangesAsync(); + } + + public async Task DeleteAuthUserByEMailAsync(string email) + { + try + { + await _authUserRepository.DeleteAuthUserByEMailAsync(email); + await _unitOfWork.SaveChangesAsync(); + _logger.Info($"Deleted authUser with email: {email}"); + } + catch (Exception e) + { + _logger.Error(e); + throw new ServiceException(ServiceErrorCode.UnableToDelete, $"Cannot delete authUser with email {email}"); + } + } + + public async Task DeleteAuthUserAsync(AuthUserDTO authUserDTO) + { + try + { + _authUserRepository.DeleteAuthUser(authUserDTO.ToAuthUser()); + await _unitOfWork.SaveChangesAsync(); + _logger.Info($"Deleted authUser {authUserDTO.EMail}"); + } + catch (Exception e) + { + _logger.Error(e); + throw new ServiceException(ServiceErrorCode.UnableToDelete, $"Cannot delete authUser {authUserDTO.EMail}"); + } + } + + private string ComputeHash(string input, HashAlgorithm algorithm) + { + Byte[] inputBytes = Encoding.UTF8.GetBytes(input); + Byte[] hashedBytes = algorithm.ComputeHash(inputBytes); + return BitConverter.ToString(hashedBytes); + } + + private bool IsValidEmail(string email) + { + try + { + var addr = new System.Net.Mail.MailAddress(email); + return addr.Address == email; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/gswi.Service/gswi.Service.csproj b/gswi.Service/gswi.Service.csproj new file mode 100644 index 0000000..9109b93 --- /dev/null +++ b/gswi.Service/gswi.Service.csproj @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + net6.0 + + \ No newline at end of file diff --git a/gswi.Share.Common/AuthUserSelectCriterion.cs b/gswi.Share.Common/AuthUserSelectCriterion.cs new file mode 100644 index 0000000..679abc7 --- /dev/null +++ b/gswi.Share.Common/AuthUserSelectCriterion.cs @@ -0,0 +1,11 @@ +namespace gswi.Share.Common +{ + public class AuthUserSelectCriterion : SelectCriterion + { + + public string FirstName { get; set; } + public string LastName { get; set; } + public string EMail { get; set; } + public int? AuthRole { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Share.Common/SelectCriterion.cs b/gswi.Share.Common/SelectCriterion.cs new file mode 100644 index 0000000..207dadd --- /dev/null +++ b/gswi.Share.Common/SelectCriterion.cs @@ -0,0 +1,10 @@ +namespace gswi.Share.Common +{ + public class SelectCriterion + { + public int PageIndex { get; set; } + public int PageSize { get; set; } + public string SortDirection { get; set; } + public string SortColumn { get; set; } + } +} \ No newline at end of file diff --git a/gswi.Share.Common/gswi.Share.Common.csproj b/gswi.Share.Common/gswi.Share.Common.csproj new file mode 100644 index 0000000..e815bd7 --- /dev/null +++ b/gswi.Share.Common/gswi.Share.Common.csproj @@ -0,0 +1,11 @@ + + + + + + + + net6.0 + + + diff --git a/gswi.SignalR/NotifyHub.cs b/gswi.SignalR/NotifyHub.cs new file mode 100644 index 0000000..d409fa7 --- /dev/null +++ b/gswi.SignalR/NotifyHub.cs @@ -0,0 +1,9 @@ +using System; +using Microsoft.AspNetCore.SignalR; + +namespace gswi.SignalR +{ + public class NotifyHub : Hub + { + } +} diff --git a/gswi.SignalR/gswi.SignalR.csproj b/gswi.SignalR/gswi.SignalR.csproj new file mode 100644 index 0000000..e11570f --- /dev/null +++ b/gswi.SignalR/gswi.SignalR.csproj @@ -0,0 +1,11 @@ + + + + net6.0 + + + + + + + diff --git a/gswi.sln b/gswi.sln new file mode 100644 index 0000000..c779244 --- /dev/null +++ b/gswi.sln @@ -0,0 +1,127 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30621.155 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi", "gswi\gswi.csproj", "{9AB6095F-23EF-4925-9637-7F0B7DE57D4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Configuration", "gswi.Configuration\gswi.Configuration.csproj", "{6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.CredentialManager", "gswi.CredentialManager\gswi.CredentialManager.csproj", "{90969785-C60C-45DB-A84E-E92A65755DF0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Data", "gswi.Data\gswi.Data.csproj", "{1F380FAE-D068-40BB-869A-80CE076A2C81}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Interface", "gswi.Interface\gswi.Interface.csproj", "{447C2465-462F-4DBC-95F8-2330BBE36C4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Model", "gswi.Model\gswi.Model.csproj", "{185EDC35-E150-4404-90EC-562819397D80}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Service", "gswi.Service\gswi.Service.csproj", "{F90A1288-0A41-449F-A856-F8988E77547F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SMTP.Interface", "gswi.SMTP.Interface\gswi.SMTP.Interface.csproj", "{EAE98478-F76B-4A37-A428-3A4F9DF18458}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SMTP.Service", "gswi.SMTP.Service\gswi.SMTP.Service.csproj", "{EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SMTP.Model", "gswi.SMTP.Model\gswi.SMTP.Model.csproj", "{7A792724-5EBF-4B12-AB8B-ACEF17F3E752}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SignalR", "gswi.SignalR\gswi.SignalR.csproj", "{B55249FD-3E4B-497B-810F-C7D60DB7A09C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Share.Common", "gswi.Share.Common\gswi.Share.Common.csproj", "{53841805-BF4C-4197-98C8-79CA3609D670}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {31207121-94AB-4E14-9533-0E14E5A1D7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31207121-94AB-4E14-9533-0E14E5A1D7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31207121-94AB-4E14-9533-0E14E5A1D7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31207121-94AB-4E14-9533-0E14E5A1D7B7}.Release|Any CPU.Build.0 = Release|Any CPU + {24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Release|Any CPU.Build.0 = Release|Any CPU + {256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Debug|Any CPU.Build.0 = Debug|Any CPU + {256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Release|Any CPU.ActiveCfg = Release|Any CPU + {256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Release|Any CPU.Build.0 = Release|Any CPU + {1B2288C0-7376-417F-B6E7-4722674A3865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B2288C0-7376-417F-B6E7-4722674A3865}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B2288C0-7376-417F-B6E7-4722674A3865}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B2288C0-7376-417F-B6E7-4722674A3865}.Release|Any CPU.Build.0 = Release|Any CPU + {4EB016C1-4193-4935-9E83-C127F6B31E90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EB016C1-4193-4935-9E83-C127F6B31E90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EB016C1-4193-4935-9E83-C127F6B31E90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EB016C1-4193-4935-9E83-C127F6B31E90}.Release|Any CPU.Build.0 = Release|Any CPU + {DE15C470-100E-4D31-B81B-C28B53C244CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE15C470-100E-4D31-B81B-C28B53C244CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE15C470-100E-4D31-B81B-C28B53C244CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE15C470-100E-4D31-B81B-C28B53C244CF}.Release|Any CPU.Build.0 = Release|Any CPU + {0D798502-DF95-453D-B3D4-A49D2536A83D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D798502-DF95-453D-B3D4-A49D2536A83D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D798502-DF95-453D-B3D4-A49D2536A83D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D798502-DF95-453D-B3D4-A49D2536A83D}.Release|Any CPU.Build.0 = Release|Any CPU + {F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Release|Any CPU.Build.0 = Release|Any CPU + {9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Release|Any CPU.Build.0 = Release|Any CPU + {6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Release|Any CPU.Build.0 = Release|Any CPU + {90969785-C60C-45DB-A84E-E92A65755DF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90969785-C60C-45DB-A84E-E92A65755DF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90969785-C60C-45DB-A84E-E92A65755DF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90969785-C60C-45DB-A84E-E92A65755DF0}.Release|Any CPU.Build.0 = Release|Any CPU + {1F380FAE-D068-40BB-869A-80CE076A2C81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F380FAE-D068-40BB-869A-80CE076A2C81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F380FAE-D068-40BB-869A-80CE076A2C81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F380FAE-D068-40BB-869A-80CE076A2C81}.Release|Any CPU.Build.0 = Release|Any CPU + {447C2465-462F-4DBC-95F8-2330BBE36C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {447C2465-462F-4DBC-95F8-2330BBE36C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {447C2465-462F-4DBC-95F8-2330BBE36C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {447C2465-462F-4DBC-95F8-2330BBE36C4E}.Release|Any CPU.Build.0 = Release|Any CPU + {185EDC35-E150-4404-90EC-562819397D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {185EDC35-E150-4404-90EC-562819397D80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {185EDC35-E150-4404-90EC-562819397D80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {185EDC35-E150-4404-90EC-562819397D80}.Release|Any CPU.Build.0 = Release|Any CPU + {F90A1288-0A41-449F-A856-F8988E77547F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F90A1288-0A41-449F-A856-F8988E77547F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F90A1288-0A41-449F-A856-F8988E77547F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F90A1288-0A41-449F-A856-F8988E77547F}.Release|Any CPU.Build.0 = Release|Any CPU + {EAE98478-F76B-4A37-A428-3A4F9DF18458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAE98478-F76B-4A37-A428-3A4F9DF18458}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAE98478-F76B-4A37-A428-3A4F9DF18458}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAE98478-F76B-4A37-A428-3A4F9DF18458}.Release|Any CPU.Build.0 = Release|Any CPU + {EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Release|Any CPU.Build.0 = Release|Any CPU + {7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Release|Any CPU.Build.0 = Release|Any CPU + {B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Release|Any CPU.Build.0 = Release|Any CPU + {53841805-BF4C-4197-98C8-79CA3609D670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53841805-BF4C-4197-98C8-79CA3609D670}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53841805-BF4C-4197-98C8-79CA3609D670}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53841805-BF4C-4197-98C8-79CA3609D670}.Release|Any CPU.Build.0 = Release|Any CPU + {539E128A-E0B2-4593-A13A-9641922A50EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {539E128A-E0B2-4593-A13A-9641922A50EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {539E128A-E0B2-4593-A13A-9641922A50EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {539E128A-E0B2-4593-A13A-9641922A50EC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {27320AC8-C9ED-452B-9C99-074215D41C63} + EndGlobalSection +EndGlobal diff --git a/gswi/Controllers/AuthController.cs b/gswi/Controllers/AuthController.cs new file mode 100644 index 0000000..189bdd0 --- /dev/null +++ b/gswi/Controllers/AuthController.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using gswi.Interface.Services; +using gswi.Model.DTOs; +using gswi.Share.Common; + +namespace gswi.Controllers +{ + [Route("api/auth")] + [ApiController] + public class AuthController : ControllerBase + { + private readonly IAuthService _authService; + + public AuthController( + IAuthService authServce + ) + { + _authService = authServce; + } + + /* Data requests */ + // Get /api/auth/users + [HttpGet("users")] + [Authorize] + public async Task> GetAllAuthUsers() + { + return await _authService.GetAllAuthUsersAsync(); + } + + // POST /api/auth/users/get/filtered + [HttpPost("users/get/filtered")] + [Authorize] + public async Task GetFilteredAuthUsers(AuthUserSelectCriterion selectCriterion) + { + return await _authService.GetFilteredAuthUsersAsync(selectCriterion); + } + + // Get /api/auth/users/get/ + [HttpGet("users/get/{email}")] + [Authorize] + public async Task GetUserFromEMail(string email) + { + return await _authService.GetAuthUserByEMailAsync(email); + } + + // Get /api/auth/users/find/ + [HttpGet("users/find/{email}")] + [Authorize] + public async Task FindUserFromEMail(string email) + { + return await _authService.FindAuthUserByEMailAsync(email); + } + + /* Auth requests */ + // POST /api/auth/register + [HttpPost("register")] + public async Task Register(AuthUserDTO userDTO) + { + await _authService.AddAuthUserAsync(userDTO); + } + + // POST /api/auth/register/ + [HttpPost("register/{id}")] + public async Task ConfirmEMail(string id) + { + return await _authService.ConfirmEMail(id); + } + + // POST /api/auth/login + [HttpPost("login")] + public async Task Login(AuthUserDTO userDTO) + { + return await _authService.Login(userDTO); + } + + // POST /api/auth/forgot-password + [HttpPost("forgot-password")] + public async Task ForgotPassword([FromBody] string email) + { + await _authService.ForgotPassword(email); + } + + // POST /api/auth/confirm-forgot-password + [HttpPost("confirm-forgot-password")] + public async Task ConfirmForgotPassword([FromBody] string id) + { + return await _authService.ConfirmForgotPassword(id); + } + + // POST /api/auth/reset-password + [HttpPost("reset-password")] + public async Task ResetPassword(ResetPasswordDTO rpDTO) + { + await _authService.ResetPassword(rpDTO); + } + + // POST /api/auth/update-user + [HttpPost("update-user")] + public async Task UpdateUser(UpdateUserDTO updateUserDTO) + { + await _authService.UpdateUser(updateUserDTO); + } + + // POST /api/auth/update-user-as-admin + [HttpPost("update-user-as-admin")] + [Authorize] + public async Task UpdateUserAsAdmin(AdminUpdateUserDTO updateUserDTO) + { + await _authService.UpdateUserAsAdmin(updateUserDTO); + } + + // POST /api/auth/refresh + [HttpPost("refresh")] + public async Task Refresh(TokenDTO tokenDTO) + { + return await _authService.Refresh(tokenDTO); + } + + // POST /api/auth/revoke + [HttpPost("revoke")] + public async Task Revoke(TokenDTO tokenDTO) + { + await _authService.Revoke(tokenDTO); + } + + // POST /api/auth/delete-user + [HttpPost("delete-user")] + public async Task DeleteAuthUserAsync(AuthUserDTO userDTO) + { + await _authService.DeleteAuthUserAsync(userDTO); + } + + // POST /api/auth/delete-user + [HttpPost("delete-user-by-mail/{mail}")] + public async Task DeleteAuthUserByEMailAsync(string mail) + { + await _authService.DeleteAuthUserByEMailAsync(mail); + } + } +} \ No newline at end of file diff --git a/gswi/Controllers/GUIController.cs b/gswi/Controllers/GUIController.cs new file mode 100644 index 0000000..2489827 --- /dev/null +++ b/gswi/Controllers/GUIController.cs @@ -0,0 +1,92 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using gswi.Configuration; +using gswi.Model.DTOs; +using gswi.SMTP.Interface; +using gswi.SMTP.Model; + +namespace gswi.Controllers +{ + [Route("api/gui")] + [ApiController] + public class GUIController : ControllerBase + { + private APISettings _apiSettings; + private DatabaseSettings _databaseSettings; + private AuthentificationSettings _authSettings; + private EMailSettings _mailSettings; + private FrontendSettings _frontendSettings; + private IHostEnvironment _env; + private readonly ISMTPClient _smtpClient; + + public GUIController( + APISettings apiSettings, + DatabaseSettings databaseSettings, + AuthentificationSettings authSettings, + EMailSettings mailSettings, + FrontendSettings frontendSettings, + IHostEnvironment env, + ISMTPClient smtpClient + ) + { + this._apiSettings = apiSettings; + this._databaseSettings = databaseSettings; + this._authSettings = authSettings; + this._mailSettings = mailSettings; + this._frontendSettings = frontendSettings; + this._env = env; + this._smtpClient = smtpClient; + } + + // GET /api/gui/api-version + [HttpGet("api-version")] + public ApiVersionDTO GetApiVersion() + { + return new ApiVersionDTO() + { + Major = this._apiSettings.ApiVersion.Major, + Minor = this._apiSettings.ApiVersion.Minor, + Micro = this._apiSettings.ApiVersion.Micro + }; + } + + // GET /api/gui/settings + [HttpGet("settings")] + [Authorize] + public SettingsDTO GetSettingsDTO() + { + return new SettingsDTO() + { + ApiVersion = this._apiSettings.ApiVersion.ToString(), + ConfigPath = this._env.ContentRootPath, + WebBaseURL = this._frontendSettings.URL, + ApiBaseURL = "", + + TokenExpireTime = this._authSettings.TokenExpireTime, + RefreshTokenExpireTime = this._authSettings.RefreshTokenExpireTime, + + MailUser = this._mailSettings.Username, + MailPort = this._mailSettings.MailServerPort, + MailHost = this._mailSettings.MailServerAddress, + MailTransceiver = this._mailSettings.FromName, + MailTransceiverAddress = this._mailSettings.FromAddress + }; + } + + // POST /api/gui/send-test-mail/ + [HttpPost("send-test-mail/{email}")] + [Authorize] + public async Task SendTestMail(string email) + { + await _smtpClient.SendEmailAsync(new EMail() + { + Receiver = email, + Subject = $"Login counter Test E-Mail", + Message = $"Login counter Test E-Mail" + }); + } + } +} diff --git a/gswi/DataSeeder.cs b/gswi/DataSeeder.cs new file mode 100644 index 0000000..3960455 --- /dev/null +++ b/gswi/DataSeeder.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Security.Cryptography; +using Microsoft.EntityFrameworkCore; +using gswi.Data; +using gswi.Model; + +namespace gswi +{ + public class DataSeeder + { + private readonly DatabaseContext _databaseContext; + + public DataSeeder(DatabaseContext databaseContext) + { + _databaseContext = databaseContext; + } + + public void SeedData() + { + _databaseContext.Database.EnsureCreated(); + + if (!_databaseContext.AuthUsers.Any()) + { + + var admin = new AuthUser() + { + FirstName = "Admin", + LastName = "Administator", + EMail = "admin@localhost", + Password = ComputeHash("Administator", new SHA256CryptoServiceProvider()), + AuthRole = AuthRoles.Admin + }; + + var authUser = new AuthUser() + { + FirstName = "Max", + LastName = "Mustermann", + EMail = "max.mustermann@gmail.com", + Password = ComputeHash("test1234", new SHA256CryptoServiceProvider()), + }; + + var authUser1 = new AuthUser() + { + FirstName = "Max", + LastName = "Tester", + EMail = "max.mustermann@mustermail.com", + Password = ComputeHash("test1234", new SHA256CryptoServiceProvider()), + }; + + var authUser2 = new AuthUser() + { + FirstName = "Max", + LastName = "Muster", + EMail = "max.mustermann@yahoo.com", + Password = ComputeHash("test1234", new SHA256CryptoServiceProvider()), + }; + + _databaseContext.AuthUsers.Add(admin); + _databaseContext.AuthUsers.Add(authUser); + _databaseContext.AuthUsers.Add(authUser1); + _databaseContext.AuthUsers.Add(authUser2); + } + _databaseContext.SaveChanges(); + } + + public string ComputeHash(string input, HashAlgorithm algorithm) + { + Byte[] inputBytes = Encoding.UTF8.GetBytes(input); + Byte[] hashedBytes = algorithm.ComputeHash(inputBytes); + return BitConverter.ToString(hashedBytes); + } + } +} diff --git a/gswi/Filters/CustomExceptionFilterAttribute.cs b/gswi/Filters/CustomExceptionFilterAttribute.cs new file mode 100644 index 0000000..d218f51 --- /dev/null +++ b/gswi/Filters/CustomExceptionFilterAttribute.cs @@ -0,0 +1,55 @@ +using System; +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using NLog; +using gswi.Model.Filters; + +namespace gswi.Filters +{ + public class CustomExceptionFilterAttribute : ExceptionFilterAttribute + { + + + private static Logger _logger = LogManager.GetCurrentClassLogger(); + + public CustomExceptionFilterAttribute() { } + + public override void OnException(ExceptionContext context) + { + context.Result = CreateJsonErrorResult(context); + base.OnException(context); + } + + public JsonResult CreateJsonErrorResult(ExceptionContext context) + { + JsonResult result = null; + + // create jsonresult + var exception = context.Exception; + if (exception is ServiceException) + { + var bex = exception as ServiceException; + _logger.Error($"{bex.GetDetailedMessage()}"); + var error = new ErrorDto(bex.ErrorCode, bex.GetMessage()); + result = new JsonResult(error) + { + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + else + { + var trackingId = Guid.NewGuid(); + string userMsg = $"Tracking Id: {trackingId}"; + _logger.Error(exception, userMsg); + var error = new ErrorDto($"Tracking Id: {trackingId}"); + result = new JsonResult(error) + { + StatusCode = (int)HttpStatusCode.InternalServerError + }; + } + return result; + } + + } +} \ No newline at end of file diff --git a/gswi/HostExtensions.cs b/gswi/HostExtensions.cs new file mode 100644 index 0000000..db8a9fa --- /dev/null +++ b/gswi/HostExtensions.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using gswi.Data; + +namespace gswi +{ + public static class HostExtensions + { + public static IHost SeedData(this IHost host) + { + using (var scope = host.Services.CreateScope()) + { + var services = scope.ServiceProvider; + var context = services.GetService(); + + // now we have the DbContext. Run migrations + context.Database.Migrate(); + + #if DEBUG + // now that the database is up to date. Let's seed + new DataSeeder(context).SeedData(); + #endif + } + + return host; + } + } +} diff --git a/gswi/JSONConverter/JsonCreationConverter.cs b/gswi/JSONConverter/JsonCreationConverter.cs new file mode 100644 index 0000000..c105330 --- /dev/null +++ b/gswi/JSONConverter/JsonCreationConverter.cs @@ -0,0 +1,54 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; + +namespace gswi.JSONConverter +{ + public abstract class JsonCreationConverter : JsonConverter + { + static Logger _logger = LogManager.GetCurrentClassLogger(); + + protected abstract T Create(Type objectType, JObject jObject); + + public override bool CanConvert(Type objectType) + { + return typeof(T).IsAssignableFrom(objectType); + } + + public override bool CanWrite + { + get { return false; } + } + + public override object ReadJson(JsonReader reader, + Type objectType, + object existingValue, + JsonSerializer serializer) + { + try + { + // Load JObject from stream + JObject jObject = JObject.Load(reader); + + // Create target object based on JObject + T target = Create(objectType, jObject); + + // Populate the object properties + serializer.Populate(jObject.CreateReader(), target); + + return target; + } + catch (Exception e) + { + _logger.Error(e); + return null; + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + + } + } +} \ No newline at end of file diff --git a/gswi/Program.cs b/gswi/Program.cs new file mode 100644 index 0000000..ec8a640 --- /dev/null +++ b/gswi/Program.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NLog.Extensions.Logging; +using gswi.Configuration; +using System; + +namespace gswi +{ + public class Program + { + static void Main(string[] args) + { + try + { + Configuration.HostExtensions.ChangeCurrentDirToProjectDir(); + var host = CreateHostBuilder(args).Build(); + host.LogHostingEnvironment(); + host.SeedData(); + host.Run(); + } + catch (Exception ex) + { + Configuration.HostExtensions.LogStartError(ex); + Console.WriteLine(ex.ToString()); + } + finally + { + NLog.LogManager.Shutdown(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }).ConfigureLogging((hostCtx, loggingBuilder) => + { + loggingBuilder.SetMinimumLevel(LogLevel.Trace); + loggingBuilder.AddNLog($"nlog-{hostCtx.HostingEnvironment.EnvironmentName}.config"); + }); + } +} diff --git a/gswi/Properties/launchSettings.json b/gswi/Properties/launchSettings.json new file mode 100644 index 0000000..e7772a9 --- /dev/null +++ b/gswi/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50843", + "sslPort": 44360 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "login_counter": { + "commandName": "Project", + "launchUrl": "weatherforecast", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/gswi/Startup.cs b/gswi/Startup.cs new file mode 100644 index 0000000..f0beba9 --- /dev/null +++ b/gswi/Startup.cs @@ -0,0 +1,186 @@ +using System.Text; +using System; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using NLog.Extensions.Logging; +using NLog; +using gswi.Configuration; +using gswi.CredentialManager; +using gswi.Data; +using gswi.Data.Repositories; +using gswi.Filters; +using gswi.Interface.Repositories; +using gswi.Interface.Services; +using gswi.Service; +using gswi.SMTP.Interface; +using gswi.SMTP.Service; +using gswi.SignalR; +using Microsoft.AspNetCore.HttpOverrides; + +namespace gswi +{ + public class Startup + { + public IConfiguration Configuration { get; } + private static Logger _logger = LogManager.GetCurrentClassLogger(); + + public Startup(IHostEnvironment env) + { + try + { + var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + if (string.IsNullOrEmpty(environmentName)) + environmentName = "production"; + var builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile($"appsettings.{System.Net.Dns.GetHostName()}.json", optional: true) + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + catch (Exception e) + { + _logger.Error(e); + } + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddLogging(builder => + { + builder.ClearProviders(); + builder.AddConfiguration(Configuration.GetSection("Logging")); + builder.AddNLog(); + builder.AddConsole(); + }); + + var frontend = GetTypedSettingsFromSection("Frontend"); + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => builder.WithOrigins(frontend.URL) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + .SetIsOriginAllowed((host) => true)); + }); + + var dbSettings = GetTypedSettingsFromSection("Database"); + var authSettings = GetTypedSettingsFromSection("Authentification"); + var emailSettings = GetTypedSettingsFromSection("EMail"); + var apiSettings = GetTypedSettingsFromSection("API"); + emailSettings.Credentials = Base64.Decode(emailSettings.Credentials); + + services.AddSingleton(dbSettings); + services.AddSingleton(authSettings); + services.AddSingleton(emailSettings); + services.AddSingleton(apiSettings); + services.AddSingleton(GetTypedSettingsFromSection("Frontend")); + + services.AddDbContextPool(opt => opt.UseMySql( + Base64.DecodeConnectionString(dbSettings.ConnectionString, dbSettings.Credentials), + new MySqlServerVersion(new Version(10, 3, 32)) + )); + + services.AddTransient(); + services.AddTransient(); + + services.AddScoped((s) => s.GetRequiredService()); + services.AddScoped((s) => new AuthUserRepositoryImpl((DatabaseContext)s.GetService())); + + services.AddSignalR(); + + services.AddAuthentication(opt => + { + opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(opt => + { + opt.RequireHttpsMetadata = false; + opt.SaveToken = true; + opt.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + + ValidIssuer = authSettings.Issuer, + ValidAudience = authSettings.Audience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey)) + }; + }); + + services.AddControllers() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; + // options.SerializerSettings.TypeNameHandling = TypeNameHandling.All; + }); + + services.AddControllers(options => + { + options.Filters.Add(typeof(CustomExceptionFilterAttribute)); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder gswi, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + gswi.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + + if (env.IsDevelopment()) + { + gswi.UseDeveloperExceptionPage(); + } + + NLog.LogManager.LoadConfiguration($"nlog-{env.EnvironmentName}.config"); + + gswi.UseCors("CorsPolicy"); + + var apiSettings = GetTypedSettingsFromSection("API"); + if (apiSettings.RedirectToHTTPS) + { + gswi.UseHttpsRedirection(); + } + + gswi.UseRouting(); + + gswi.UseAuthentication(); + gswi.UseAuthorization(); + + gswi.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapHub("/notify"); + }); + } + + protected SettingType GetTypedSettingsFromSection(string sectionName) where SettingType : new() + { + try + { + var settings = new SettingType(); + Configuration.GetSection(sectionName).Bind(settings); + return settings; + } + catch (Exception e) + { + _logger.Error(e); + return new SettingType(); + } + } + } +} diff --git a/gswi/appsettings.json b/gswi/appsettings.json new file mode 100644 index 0000000..ec1a4c0 --- /dev/null +++ b/gswi/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "API": { + "RedirectToHTTPS": true, + "ApiVersion": { + "Major": "HEAD", + "Minor": "0", + "Micro": "0" + } + }, + "AllowedHosts": "*" +} diff --git a/gswi/gswi.csproj b/gswi/gswi.csproj new file mode 100644 index 0000000..d257a7c --- /dev/null +++ b/gswi/gswi.csproj @@ -0,0 +1,43 @@ + + + net6.0 + gswi + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gswi/nlog-development.config b/gswi/nlog-development.config new file mode 100644 index 0000000..f126214 --- /dev/null +++ b/gswi/nlog-development.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gswi/nlog-production.config b/gswi/nlog-production.config new file mode 100644 index 0000000..55dfe8d --- /dev/null +++ b/gswi/nlog-production.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gswi/nlog-staging.config b/gswi/nlog-staging.config new file mode 100644 index 0000000..55dfe8d --- /dev/null +++ b/gswi/nlog-staging.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gswi/update-version.ps1 b/gswi/update-version.ps1 new file mode 100644 index 0000000..a31536f --- /dev/null +++ b/gswi/update-version.ps1 @@ -0,0 +1,52 @@ +#! /snap/bin/pwsh + +function Get-VersionFromBranch { + $branch = git rev-parse --abbrev-ref HEAD + $versions = $branch.Split('.') + $major = "0" + $minor = "0" + $micro = "0" + + if ($versions.Count -gt 0) { + $major = $versions[0] + } + + if ($versions.Count -gt 1) { + $minor = $versions[1] + } + + if ($versions.Count -gt 2) { + $micro = $versions[2] + } + + return $major, $minor, $micro +} + +function Get-VersionAsJSON([string]$major, [string]$minor, [string]$micro) { + return @{ + "Major" = "$major" + "Minor" = "$minor" + "Micro" = "$micro" + } | ConvertTo-Json +} + +function Get-AppsettingsAsJSON() { + return Get-Content -Raw -Path "./appsettings.json" | ConvertFrom-Json +} + +function Set-AppsettingsAsJSON($settings) { + $settings | ConvertTo-Json | Out-File "./appsettings.json" +} + +function Set-VersionFromBranch([string]$major, [string]$minor, [string]$micro) { + $versionJSON = Get-VersionAsJSON $major $minor $micro + $appsettings = Get-AppsettingsAsJSON + + $appsettings.API.ApiVersion.Major = $major + $appsettings.API.ApiVersion.Minor = $minor + $appsettings.API.ApiVersion.Micro = $micro + Set-AppsettingsAsJSON $appsettings +} + +$res = Get-VersionFromBranch +Set-VersionFromBranch $res[0] $res[1] $res[2]