diff --git a/csharp/App/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj index fc491daee..bbbac1191 100644 --- a/csharp/App/Backend/Backend.csproj +++ b/csharp/App/Backend/Backend.csproj @@ -7,6 +7,7 @@ + @@ -34,213 +35,20 @@ - + PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Never + diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 0c4af1a87..b9b5d3878 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -1,5 +1,3 @@ -using System.Runtime.InteropServices.ComTypes; -using System.Text.Json.Nodes; using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes.Methods; @@ -21,17 +19,12 @@ public class Controller : ControllerBase var user = Db.GetUserByEmail(username); if (user is null) - { - throw new Exceptions(400,"Null User Exception", "Must provide a user to log in as.", Request.Path.Value!); - } + throw new Exceptions(400, "Null User Exception", "Must provide a user to log in as.", Request.Path.Value!); - if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword)) + if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword) && !user.VerifyPassword(password)) { - if (!user.VerifyPassword(password)) - { - //return Unauthorized("No Password set"); - throw new Exceptions(401,"Wrong Password Exception", "Please try again.", Request.Path.Value!); - } + //return Unauthorized("No Password set"); + throw new Exceptions(401, "Wrong Password Exception", "Please try again.", Request.Path.Value!); } var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user)); @@ -67,7 +60,9 @@ public class Controller : ControllerBase if (user is null || !session.HasAccessTo(user)) return Unauthorized(); - return user.HidePassword().HideParentIfUserHasNoAccessToParent(session); + return user + .HidePassword() + .HideParentIfUserHasNoAccessToParent(session); } @@ -102,10 +97,10 @@ public class Controller : ControllerBase return Unauthorized(); return installation - .UsersWithDirectAccess() - .Where(u => u.IsDescendantOf(user)) - .Select(u => u.HidePassword()) - .ToList(); + .UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => u.HidePassword()) + .ToList(); } [HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))] @@ -121,11 +116,11 @@ public class Controller : ControllerBase return Unauthorized(); return installation - .Ancestors() - .SelectMany(f => f.UsersWithDirectAccess() - .Where(u => u.IsDescendantOf(user)) - .Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() })) - .ToList(); + .Ancestors() + .SelectMany(f => f.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() })) + .ToList(); } [HttpGet(nameof(GetUsersWithDirectAccessToFolder))] @@ -255,11 +250,11 @@ public class Controller : ControllerBase [HttpPost(nameof(CreateUser))] - public ActionResult CreateUser([FromBody] User newUser, Token authToken) + public async Task> CreateUser([FromBody] User newUser, Token authToken) { var create = Db.GetSession(authToken).Create(newUser); - return create && Db.SendNewUserEmail(newUser) + return create && await Db.SendNewUserEmail(newUser) ? newUser.HidePassword() : Unauthorized() ; } @@ -326,8 +321,8 @@ public class Controller : ControllerBase var user = Db.GetUserById(installationAccess.UserId); return session.GrantUserAccessTo(user, installation) - ? Ok() - : Unauthorized(); + ? Ok() + : Unauthorized(); } [HttpPost(nameof(RevokeUserAccessToInstallation))] @@ -461,18 +456,19 @@ public class Controller : ControllerBase } [HttpPost(nameof(ResetPasswordRequest))] - public ActionResult> ResetPasswordRequest(String username) + public async Task>> ResetPasswordRequest(String email) { - var user = Db.GetUserByEmail(username); + var user = Db.GetUserByEmail(email); if (user is null) return Unauthorized(); var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user)); - var res = Db.Create(session); - return res && Db.SendPasswordResetEmail(user, session.Token) - ? Ok() - : Unauthorized(); + var success = Db.Create(session); + + return success && await Db.SendPasswordResetEmail(user, session.Token) + ? Ok() + : Unauthorized(); } diff --git a/csharp/App/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs index c3beece1a..b85020cfe 100644 --- a/csharp/App/Backend/DataTypes/Methods/User.cs +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -1,6 +1,6 @@ -using System.Net.Mail; using System.Security.Cryptography; using InnovEnergy.App.Backend.Database; +using InnovEnergy.Lib.Mailer; using InnovEnergy.Lib.Utils; using Convert = System.Convert; using static System.Text.Encoding; @@ -91,13 +91,14 @@ public static class UserMethods .Skip(1); // skip self } - public static Boolean VerifyPassword(this User user, String password) + public static Boolean VerifyPassword(this User user, String? password) { - return Db.GetUserByEmail(user.Email)?.Password == user.SaltAndHashPassword(password); + return password is not null + && Db.GetUserByEmail(user.Email)?.Password == user.SaltAndHashPassword(password); } - public static String? SaltAndHashPassword(this User user, String password) + public static String SaltAndHashPassword(this User user, String password) { var dataToHash = $"{password}{user.Salt()}"; @@ -150,8 +151,10 @@ public static class UserMethods if (installation is null) return false; - return user.HasDirectAccessTo(installation) || - installation.Ancestors().Any(user.HasDirectAccessTo); + return user.HasDirectAccessTo(installation) + || installation + .Ancestors() + .Any(user.HasDirectAccessTo); } public static Boolean HasAccessTo(this User user, User? other) @@ -159,10 +162,8 @@ public static class UserMethods if (other is null) return false; - if (other.Id == user.Id) - return true; - - return other + return other.Id == user.Id + || other .Ancestors() .Contains(user); } @@ -172,9 +173,9 @@ public static class UserMethods return other?.Type switch { "installation" => user.HasAccessTo((Installation)other), - "user" => user.HasAccessTo((User)other), - "folder" => user.HasAccessTo((Folder)other), - _ => false + "user" => user.HasAccessTo((User)other), + "folder" => user.HasAccessTo((Folder)other), + _ => false }; } @@ -183,13 +184,11 @@ public static class UserMethods return other?.Type switch { "Installation" => user.HasAccessTo(Db.GetFolderById(other.ParentId)), - "User" => user.HasAccessTo(Db.GetUserById(other.ParentId)), - "Folder" => user.HasAccessTo(Db.GetFolderById(other.ParentId)), - _ => false + "User" => user.HasAccessTo(Db.GetUserById(other.ParentId)), + "Folder" => user.HasAccessTo(Db.GetFolderById(other.ParentId)), + _ => false }; } - - private static String Salt(this User user) { @@ -211,4 +210,34 @@ public static class UserMethods return user; } + public static Task SendEmail(this User user, String subject, String body) + { + return Mailer.Send(user.Name, user.Email, subject, body); + } + + public static Task SendPasswordResetEmail(this User user, String token) + { + const String subject = "Reset the password of your InnovEnergy-Account"; + const String resetLink = "https://monitor.innov.energy/api/ResetPassword"; // TODO: move to settings file + + var body = $"Dear {user.Name}\n" + + $"To reset your password " + + $"please open this link:{resetLink}?token={token}"; + + return user.SendEmail(subject, body); + } + + public static Task SendNewUserWelcomeMessage(this User user) + { + const String subject = "Your new InnovEnergy-Account"; + + var resetLink = $"https://monitor.innov.energy/?username={user.Email}"; // TODO: move to settings file + + var body = $"Dear {user.Name}\n" + + $"To set your password and log in to your " + + $"Innovenergy-Account open this link:{resetLink}"; + + return user.SendEmail(subject, body); + } + } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 425389706..b8d86c071 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -1,12 +1,11 @@ using System.Reactive.Concurrency; using System.Reactive.Linq; -using CliWrap; -using CliWrap.Buffered; using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes.Methods; using InnovEnergy.App.Backend.Relations; using InnovEnergy.Lib.S3Utils; using InnovEnergy.Lib.S3Utils.DataTypes; +using InnovEnergy.Lib.Utils; using SQLite; using SQLiteConnection = SQLite.SQLiteConnection; @@ -38,35 +37,15 @@ public static partial class Db memoryConnection.CreateTable(); memoryConnection.CreateTable(); memoryConnection.CreateTable(); + + fileConnection.Table ().ForEach(memoryConnection.Insert); + fileConnection.Table ().ForEach(memoryConnection.Insert); + fileConnection.Table ().ForEach(memoryConnection.Insert); + fileConnection.Table ().ForEach(memoryConnection.Insert); + fileConnection.Table ().ForEach(memoryConnection.Insert); + fileConnection.Table ().ForEach(memoryConnection.Insert); + fileConnection.Table().ForEach(memoryConnection.Insert); - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } - foreach (var obj in fileConnection.Table()) - { - memoryConnection.Insert(obj); - } return memoryConnection; }))(); @@ -152,34 +131,53 @@ public static partial class Db private static async Task UpdateS3Urls() { var regions = Installations - .Select(i => i.S3Region) - .Distinct().ToList(); + .Select(i => i.S3Region) + .Distinct() + .ToList(); + const String provider = "exo.io"; + foreach (var region in regions) { - var bucketList = await new S3Region($"https://{region}.{provider}", ExoCmd.S3Creds!).ListAllBuckets(); + var s3Region = new S3Region($"https://{region}.{provider}", ExoCmd.S3Creds!); + var bucketList = await s3Region.ListAllBuckets(); - foreach (var bucket in bucketList.Buckets) + var installations = from bucket in bucketList.Buckets + from installation in Installations + where installation.BucketName() == bucket.BucketName + select installation; + + foreach (var installation in installations) { - foreach (var installation in Installations) - { - if (installation.BucketName() == bucket.BucketName) - { - await installation.RenewS3Credentials(); - } - } + await installation.RenewS3Credentials(); } } } - public static Boolean SendPasswordResetEmail(User user, String sessionToken) + public static async Task SendPasswordResetEmail(User user, String sessionToken) { - return Email.Email.SendPasswordResetMessage(user, sessionToken); + try + { + await user.SendPasswordResetEmail(sessionToken); + return true; + } + catch + { + return false; + } } - public static Boolean SendNewUserEmail(User user) + public static async Task SendNewUserEmail(User user) { - return Email.Email.SendNewUserMessage(user); + try + { + await user.SendNewUserWelcomeMessage(); + return true; + } + catch + { + return false; + } } public static Boolean DeleteUserPassword(User user) diff --git a/csharp/App/Backend/Database/Read.cs b/csharp/App/Backend/Database/Read.cs index dcaa0e90d..14758c1b1 100644 --- a/csharp/App/Backend/Database/Read.cs +++ b/csharp/App/Backend/Database/Read.cs @@ -1,5 +1,4 @@ using InnovEnergy.App.Backend.DataTypes; -using InnovEnergy.App.Backend.DataTypes.Methods; using InnovEnergy.App.Backend.Relations; @@ -26,10 +25,10 @@ public static partial class Db .FirstOrDefault(u => u.Id == id); } - public static User? GetUserByEmail(String userName) + public static User? GetUserByEmail(String email) { return Users - .FirstOrDefault(u => u.Email == userName); + .FirstOrDefault(u => u.Email == email); } public static Session? GetSession(String token) diff --git a/csharp/App/Backend/Email/Email.cs b/csharp/App/Backend/Email/Email.cs deleted file mode 100644 index c1c466f39..000000000 --- a/csharp/App/Backend/Email/Email.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using InnovEnergy.App.Backend.DataTypes; -using InnovEnergy.Lib.Mailer; -using JsonSerializer = System.Text.Json.JsonSerializer; - -namespace InnovEnergy.App.Backend.Email; -public static class Email - { - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - public static Boolean SendVerificationMessage(User emailRecipientUser) - { - var config = JsonSerializer.Deserialize(File.OpenRead("./Resources/smtpConfig.json"))!; - var mailer = new Mailer(); - - mailer.From("InnovEnergy", "noreply@innov.energy"); - mailer.To(emailRecipientUser.Name, emailRecipientUser.Email); - - mailer.Subject("Create a new password for your Innovenergy-Account"); - mailer.Body("Dear " + emailRecipientUser.Name + - "\n Please create a new password for your Innovenergy-account." + - "\n To do this just login at https://HEEEEELP"); - - return mailer.SendEmailUsingSmtpConfig(config); - } - - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - public static Boolean SendPasswordResetMessage (User emailRecipientUser, String token) - { - var config = JsonSerializer.Deserialize(File.OpenRead("./Resources/smtpConfig.json"))!; - - //todo am I right? - const String resetLink = "https://monitor.innov.energy/api/ResetPassword"; - var mailer = new Mailer(); - - try - { - - mailer.From("InnovEnergy", "noreply@innov.energy"); - mailer.To(emailRecipientUser.Name, emailRecipientUser.Email); - - mailer.Subject("Reset the password of your Innovenergy-Account"); - mailer.Body("Dear " + emailRecipientUser.Name - + "\n To reset your password open this link:" - + resetLink + "?token=" - + token); - - return mailer.SendEmailUsingSmtpConfig(config); - } - catch (Exception) - { - return false; - } - } - - - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] - public static Boolean SendNewUserMessage (User emailRecipientUser) - { - var config = JsonSerializer.Deserialize(File.OpenRead("./Resources/smtpConfig.json"))!; - - //todo am I right? - var resetLink = $"https://monitor.innov.energy/?username={emailRecipientUser.Email}"; - var mailer = new Mailer(); - - try - { - - mailer.From("InnovEnergy", "noreply@innov.energy"); - mailer.To(emailRecipientUser.Name, emailRecipientUser.Email); - - mailer.Subject("Your new Innovenergy-Account"); - mailer.Body("Dear " + emailRecipientUser.Name - + "\n To set your password and log in to your Innovenergy-Account open this link:" - + resetLink); - - return mailer.SendEmailUsingSmtpConfig(config); - } - catch (Exception) - { - return false; - } - } - } diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index f39e5e463..455a9ba8b 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -3,7 +3,6 @@ using InnovEnergy.App.Backend.Database; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; -using System.Net; using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.Backend; @@ -20,7 +19,7 @@ public static class Program builder.Services.AddProblemDetails(setup => { //This includes the stacktrace in Development Env - setup.IncludeExceptionDetails = (ctx, env) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging(); + setup.IncludeExceptionDetails = (_, _) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging(); //This handles our Exceptions setup.Map(exception => new ProblemDetails @@ -43,8 +42,6 @@ public static class Program app.Use(async (context, next) => { - var x = 2; - context.Request.WriteLine(); await next(context); diff --git a/csharp/App/S3Explorer/S3Explorer.csproj b/csharp/App/S3Explorer/S3Explorer.csproj index a365244ed..920519e5e 100644 --- a/csharp/App/S3Explorer/S3Explorer.csproj +++ b/csharp/App/S3Explorer/S3Explorer.csproj @@ -14,7 +14,7 @@ - + diff --git a/csharp/Lib/Mailer/Mailer.cs b/csharp/Lib/Mailer/Mailer.cs index bb3659b54..74781ce14 100644 --- a/csharp/Lib/Mailer/Mailer.cs +++ b/csharp/Lib/Mailer/Mailer.cs @@ -1,55 +1,41 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using MailKit.Net.Smtp; using MimeKit; namespace InnovEnergy.Lib.Mailer; -public class Mailer +public static class Mailer { - private static MimeMessage Email = new(); - - public MimeMessage To(String name, String emailAddress) + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] + public static async Task Send(String recipientName, String recipientEmailAddress, String subject, String body) { - Email.To.Add(new MailboxAddress(name, emailAddress)); - return Email; - } - - public MimeMessage From(String name, String emailAddress) - { - Email.From.Add(new MailboxAddress(name, emailAddress)); - return Email; - } - - public MimeMessage Subject(String subjectText) - { - Email.Subject = subjectText; - return Email; - } - - public MimeMessage Body(String bodyText) - { - Email.Body = new TextPart(MimeKit.Text.TextFormat.Plain) + var config = await ReadMailerConfig(); + + var from = new MailboxAddress(config!.SenderName, config.SenderAddress); + var to = new MailboxAddress(recipientName, recipientEmailAddress); + + var msg = new MimeMessage { - Text = bodyText + From = { from }, + To = { to }, + Subject = subject, + Body = new TextPart { Text = body } }; - return Email; + + using var smtp = new SmtpClient(); + + await smtp.ConnectAsync(config.SmtpServerUrl, config.SmtpPort, false); + await smtp.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword); + await smtp.SendAsync(msg); + await smtp.DisconnectAsync(true); } - public Boolean SendEmailUsingSmtpConfig(SmtpConfig config) + [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.DeserializeAsync(Stream, JsonSerializerOptions, CancellationToken)")] + private static async Task ReadMailerConfig() { - try{ - using var smtp = new SmtpClient(); - smtp.Connect(config.Url, config.Port, false); - - smtp.Authenticate(config.Username, config.Password); - - smtp.Send(Email); - smtp.Disconnect(true); - } - catch (Exception) - { - return false; - } - return true; + await using var fileStream = File.OpenRead(MailerConfig.DefaultFile); + return await JsonSerializer.DeserializeAsync(fileStream); } } \ No newline at end of file diff --git a/csharp/Lib/Mailer/Mailer.csproj b/csharp/Lib/Mailer/Mailer.csproj index bebf4a19d..2f74d8390 100644 --- a/csharp/Lib/Mailer/Mailer.csproj +++ b/csharp/Lib/Mailer/Mailer.csproj @@ -7,4 +7,16 @@ + + + + true + PreserveNewest + PreserveNewest + + + + + + diff --git a/csharp/Lib/Mailer/MailerConfig.cs b/csharp/Lib/Mailer/MailerConfig.cs new file mode 100644 index 000000000..68e611595 --- /dev/null +++ b/csharp/Lib/Mailer/MailerConfig.cs @@ -0,0 +1,17 @@ +using System.Diagnostics.CodeAnalysis; +namespace InnovEnergy.Lib.Mailer; + + +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] +public class MailerConfig +{ + public required String SmtpServerUrl { get; init; } + public required String SmtpUsername { get; init; } + public required String SmtpPassword { get; init; } + public UInt16 SmtpPort { get; init; } = 587; + + public required String SenderName { get; init; } + public required String SenderAddress { get; init; } + + public const String DefaultFile = $"{nameof(MailerConfig)}.json"; +} \ No newline at end of file diff --git a/csharp/Lib/Mailer/MailerConfig.json b/csharp/Lib/Mailer/MailerConfig.json new file mode 100644 index 000000000..a7a30b2eb --- /dev/null +++ b/csharp/Lib/Mailer/MailerConfig.json @@ -0,0 +1,9 @@ +{ + "SmtpServerUrl" : "mail.agenturserver.de", + "SmtpUsername" : "p518526p69", + "SmtpPassword" : "i;b*xqm4iB5uhl", + "SmtpPort" : 587, + "SenderName" : "InnovEnergy", + "SenderAddress" : "noreply@innov.energy" +} + diff --git a/csharp/Lib/Mailer/SmtpConfig.cs b/csharp/Lib/Mailer/SmtpConfig.cs deleted file mode 100644 index 22fdb84bd..000000000 --- a/csharp/Lib/Mailer/SmtpConfig.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -namespace InnovEnergy.Lib.Mailer; - - -[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] -public class SmtpConfig -{ - public String Url { get; init; } = null!; - public String Username { get; init; } = null!; - public String Password { get; init; } = null!; - public Int32 Port { get; init; } = 587; -} \ No newline at end of file diff --git a/csharp/Lib/S3Utils/S3.cs b/csharp/Lib/S3Utils/S3.cs index 71decbd73..b8d2db382 100644 --- a/csharp/Lib/S3Utils/S3.cs +++ b/csharp/Lib/S3Utils/S3.cs @@ -42,11 +42,11 @@ public static class S3 .Select(o => new S3Url(o.Key, bucket)); } - public static async Task ListAllBuckets(this S3Region region) + public static Task ListAllBuckets(this S3Region region) { - return await region - .GetS3Client() - .ListBucketsAsync(); + return region + .GetS3Client() + .ListBucketsAsync(); } public static Task PutObject(this S3Url path, String data, Encoding encoding) => path.PutObject(encoding.GetBytes(data));