diff --git a/csharp/App/Backend/Controllers/Controller.cs b/csharp/App/Backend/Controllers/Controller.cs index 39aa52ccb..322242871 100644 --- a/csharp/App/Backend/Controllers/Controller.cs +++ b/csharp/App/Backend/Controllers/Controller.cs @@ -1,438 +1,283 @@ -using System.Net; -using System.Text; -using System.Web.Http; using InnovEnergy.App.Backend.Database; -using InnovEnergy.App.Backend.Model; -using InnovEnergy.App.Backend.Model.Relations; -using InnovEnergy.App.Backend.Utils; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; using Microsoft.AspNetCore.Mvc; -using HttpContextAccessor = Microsoft.AspNetCore.Http.HttpContextAccessor; +using static System.Net.HttpStatusCode; +using static System.String; +using Folder = InnovEnergy.App.Backend.DataTypes.Folder; +using Installation = InnovEnergy.App.Backend.DataTypes.Installation; +using Object = System.Object; +using User = InnovEnergy.App.Backend.DataTypes.User; namespace InnovEnergy.App.Backend.Controllers; [ApiController] -[Microsoft.AspNetCore.Mvc.Route("api/")] +[Route("api/")] public class Controller { + private static readonly HttpResponseMessage _Unauthorized = new HttpResponseMessage(Unauthorized); + private static readonly HttpResponseMessage _Ok = new HttpResponseMessage(OK); + private static readonly HttpResponseMessage _BadRequest = new HttpResponseMessage(BadRequest); + [Returns] - [Returns(HttpStatusCode.Unauthorized)] - [Returns(HttpStatusCode.BadRequest)] - [Microsoft.AspNetCore.Mvc.HttpPost($"{nameof(Login)}")] + [Returns(Unauthorized)] + [Returns(BadRequest)] + [HttpPost($"{nameof(Login)}")] public Object Login(Credentials credentials) { - if (String.IsNullOrWhiteSpace(credentials.Username) || - String.IsNullOrWhiteSpace(credentials.Password)) - return new HttpResponseException(HttpStatusCode.BadRequest); + var session = credentials.Login(); - using var db = Db.Connect(); - var user = db.GetUserByEmail(credentials.Username); - - - if (user is null) - return new HttpResponseException(HttpStatusCode.BadRequest); - - if (!VerifyPassword(credentials.Password, user)) - return new HttpResponseException(HttpStatusCode.Unauthorized); - - var ses = new Session(user); - db.NewSession(ses); - return new {ses.Token, user.Language}; + return session is null + ? _Unauthorized + : session; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPost($"{nameof(Logout)}")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(Logout)}")] public Object Logout() { - var caller = GetCaller(); + var session = GetSession(); - if (caller is null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - return db.DeleteSession(caller.Id); - } - - - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetInstallationS3Key)}")] - public Object GetInstallationS3Key(Int64 installationId) - { - var caller = GetCaller(); - if (caller is null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var installation = db - .GetAllAccessibleInstallations(caller) - .FirstOrDefault(i => i.Id == installationId); - - if(installation == null) - { - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - } - - var key = db.GetInstallationS3Key(installationId); - return key ?? db.CreateAndSaveInstallationS3ApiKey(installation); + return session.Logout() + ? _Ok + : _Unauthorized; } - [Returns] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetUserById)}")] - public Object GetUserById(Int64 id) - { - var caller = GetCaller(); - if (caller is null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); + // [Returns] + // [Returns(HttpStatusCode.Unauthorized)] + // [HttpGet($"{nameof(GetUserById)}")] + // public Object GetUserById(Int64 id) + // { + // var caller = GetCaller(); + // if (caller is null) + // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + // + // var user = Db.GetUserById(id); + // + // if (user is null || !caller.HasAccessTo(user)) + // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + // + // return user; + // } - var user = db - .GetDescendantUsers(caller) - .FirstOrDefault(u => u.Id == id); - - return user as Object ?? new HttpResponseMessage(HttpStatusCode.Unauthorized); - } + // + // [Returns] + // [Returns(HttpStatusCode.Unauthorized)] + // [HttpGet($"{nameof(GetInstallationById)}")] + // public Object GetInstallationById(Int64 id) + // { + // var caller = GetCaller(); + // if (caller == null) + // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + // + // var installation = Db.GetInstallationById(id); + // + // if (installation is null || !caller.HasAccessTo(installation)) + // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + // + // return installation; + // } - [Returns] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetInstallationById)}")] - public Object GetInstallationById(Int64 id) - { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var installation = db - .GetAllAccessibleInstallations(caller) - .FirstOrDefault(i => i.Id == id); - - return installation as Object ?? new HttpResponseMessage(HttpStatusCode.NotFound); - } - - - [Returns] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetFolderById)}")] - public Object GetFolderById(Int64 id) - { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var folder = db - .GetAllAccessibleFolders(caller) - .FirstOrDefault(f => f.Id == id); - - return folder as Object ?? new HttpResponseMessage(HttpStatusCode.NotFound); - } + // [Returns] + // [Returns(HttpStatusCode.Unauthorized)] + // [HttpGet($"{nameof(GetFolderById)}")] + // public Object GetFolderById(Int64 id) + // { + // var caller = GetCaller(); + // if (caller == null) + // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + // + // var folder = Db.GetFolderById(id); + // + // if (folder is null || !caller.HasAccessTo(folder)) + // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + // + // return folder; + // } [Returns] // assuming swagger knows about arrays but not lists (JSON) - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetAllInstallations)}/")] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetAllInstallations)}/")] public Object GetAllInstallations() { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); + var user = GetSession()?.User; - return db - .GetAllAccessibleInstallations(caller) - .ToList(); // important! + return user is null + ? _Unauthorized + : user.AccessibleInstallations(); } [Returns] // assuming swagger knows about arrays but not lists (JSON) - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetAllFolders)}/")] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetAllFolders)}/")] public Object GetAllFolders() { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - return db - .GetAllAccessibleFolders(caller) - .ToList(); // important! + var user = GetSession()?.User; + + return user is null + ? _Unauthorized + : user.AccessibleFolders(); } - [Returns] // assuming swagger knows about arrays but not lists (JSON) - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetTree)}/")] - public Object GetTree() - { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var folders = db - .GetDirectlyAccessibleFolders(caller) // ReSharper disable once AccessToDisposedClosure - .Select(f => PopulateChildren(db, f)); - - var installations = db.GetDirectlyAccessibleInstallations(caller); - - return folders - .Concat(installations) - .ToList(); // important! - } + // [Returns] // assuming swagger knows about arrays but not lists (JSON) + // [Returns(Unauthorized)] + // [HttpGet($"{nameof(GetUsersOfFolder)}/")] + // public Object GetUsersOfFolder(Int64 folderId) + // { + // var caller = GetCaller(); + // if (caller == null) + // return new HttpResponseMessage(Unauthorized); + // + // var folder = Db.GetFolderById(folderId); + // + // if (folder is null || !caller.HasAccessTo(folder)) + // return new HttpResponseMessage(Unauthorized); + // + // return descendantUsers; + // } [Returns] // assuming swagger knows about arrays but not lists (JSON) - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpGet($"{nameof(GetAllFoldersAndInstallations)}/")] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetAllFoldersAndInstallations)}/")] public Object GetAllFoldersAndInstallations() { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var folders = db.GetAllAccessibleFolders(caller) as IEnumerable; - var installations = db.GetAllAccessibleInstallations(caller); + var user = GetSession()?.User; - return folders - .Concat(installations) - .ToList(); // important! + return user is null + ? _Unauthorized + : user.AccessibleFoldersAndInstallations(); } - - private static Folder PopulateChildren(Db db, Folder folder, HashSet? hs = null) - { - // TODO: remove cycle detector - hs ??= new HashSet(); - if (!hs.Add(folder.Id)) - throw new Exception("Cycle detected: folder " + folder.Id); - var installations = db.GetChildInstallations(folder); - var folders = db - .GetChildFolders(folder) - .Select(c => PopulateChildren(db, c, hs)); - - folder.Children = folders.Concat(installations).ToList(); - - return folder; - } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPost($"{nameof(CreateUser)}/")] + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(CreateUser)}/")] public Object CreateUser(User newUser) { - var caller = GetCaller(); - using var db = Db.Connect(); - if (caller == null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); + var session = GetSession(); - newUser.ParentId = caller.Id; - - return db.CreateUser(newUser); + return session.Create(newUser) + ? newUser + : _Unauthorized ; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPost($"{nameof(CreateInstallation)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(CreateInstallation)}/")] public Object CreateInstallation(Installation installation) { - var caller = GetCaller(); - using var db = Db.Connect(); - if (caller == null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - var id = db.CreateInstallation(installation); - - return db.AddToAccessibleInstallations(caller.Id, id); + var session = GetSession(); + return session.Create(installation) + ? installation + : _Unauthorized; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPost($"{nameof(CreateFolder)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [Returns(InternalServerError)] + [HttpPost($"{nameof(CreateFolder)}/")] public Object CreateFolder(Folder folder) { - var caller = GetCaller(); - using var db = Db.Connect(); - if (caller == null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - var id = db.CreateFolder(folder); - return db.AddToAccessibleFolders(caller.Id, id); + var session = GetSession(); + return session.Create(folder) + ? folder + : _Unauthorized; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPut($"{nameof(UpdateUser)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPut($"{nameof(UpdateUser)}/")] public Object UpdateUser(User updatedUser) { - var caller = GetCaller(); - using var db = Db.Connect(); - if (caller == null || !db.IsParentOfChild(caller.Id, updatedUser) || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - return db.UpdateUser(updatedUser); + var session = GetSession(); + + return session.Update(updatedUser) + ? updatedUser + : _Unauthorized; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPut($"{nameof(UpdateInstallation)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPut($"{nameof(UpdateInstallation)}/")] public Object UpdateInstallation(Installation installation) { - var caller = GetCaller(); + var session = GetSession(); - if (caller is null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var installationFromAccessibleInstallations = db - .GetAllAccessibleInstallations(caller) - .FirstOrDefault(i => i.Id == installation.Id); - - if (installationFromAccessibleInstallations == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - // TODO: accessibility by other users etc - // TODO: sanity check changes - // foreach(var property in installationFromAccessibleInstallations.GetType().GetProperties()){ - // if(installation.GetType().GetProperties().Contains(property)) - // { - // property.SetValue(installationFromAccessibleInstallations, property.GetValue(installation)); - // } - // } - - return db.UpdateInstallation(installation); + return session.Update(installation) + ? installation + : _Unauthorized; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpPut($"{nameof(UpdateFolder)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPut($"{nameof(UpdateFolder)}/")] public Object UpdateFolder(Folder folder) { - var caller = GetCaller(); + var session = GetSession(); - if (caller is null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var installationFromAccessibleFolders = db - .GetAllAccessibleFolders(caller) - .FirstOrDefault(f => f.Id == folder.Id); - - if (installationFromAccessibleFolders == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - // TODO: accessibility by other users etc - // TODO: sanity check changes - - // foreach(var property in installationFromAccessibleFolders.GetType().GetProperties()){ - // if(folder.GetType().GetProperties().Contains(property)) - // { - // property.SetValue(installationFromAccessibleFolders, property.GetValue(folder)); - // } - // } - - return db.UpdateFolder(installationFromAccessibleFolders); + return session.Update(folder) + ? folder + : _Unauthorized; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpDelete($"{nameof(DeleteUser)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpDelete($"{nameof(DeleteUser)}/")] public Object DeleteUser(Int64 userId) { - var caller = GetCaller(); - - if (caller is null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); - - var userToBeDeleted = db - .GetDescendantUsers(caller) - .FirstOrDefault(u => u.Id == userId); + var session = GetSession(); + var user = Db.GetUserById(userId); - if (userToBeDeleted is null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - return db.DeleteUser(userToBeDeleted); + return session.Delete(user) + ? _Ok + : _Unauthorized; } - [Returns(HttpStatusCode.OK)] - [Returns(HttpStatusCode.Unauthorized)] - [Microsoft.AspNetCore.Mvc.HttpDelete($"{nameof(DeleteInstallation)}/")] + [Returns(OK)] + [Returns(Unauthorized)] + [HttpDelete($"{nameof(DeleteInstallation)}/")] public Object DeleteInstallation(Int64 installationId) { - var caller = GetCaller(); - - if (caller is null || !caller.HasWriteAccess) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); + var session = GetSession(); + var installation = Db.GetInstallationById(installationId); - var installationToBeDeleted = db - .GetAllAccessibleInstallations(caller) - .FirstOrDefault(i => i.Id == installationId); - - if (installationToBeDeleted is null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - return db.DeleteInstallation(installationToBeDeleted); + return session.Delete(installation) + ? _Ok + : _Unauthorized; } - [ProducesResponseType(200)] [ProducesResponseType(401)] - [Microsoft.AspNetCore.Mvc.HttpDelete($"{nameof(DeleteFolder)}/")] + [HttpDelete($"{nameof(DeleteFolder)}/")] public Object DeleteFolder(Int64 folderId) { - var caller = GetCaller(); - if (caller == null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var db = Db.Connect(); + var session = GetSession(); - var folderToDelete = db - .GetAllAccessibleFolders(caller) - .FirstOrDefault(f => f.Id == folderId); + var folder = Db.GetFolderById(folderId); + + return session.Delete(folder) + ? _Ok + : _Unauthorized; - if (folderToDelete is null) - return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - return db.DeleteFolder(folderToDelete); } - - private static User? GetCaller() + private static Session? GetSession() { var ctxAccessor = new HttpContextAccessor(); - return ctxAccessor.HttpContext?.Items["User"] as User; + return ctxAccessor.HttpContext?.Items["Session"] as Session; } - - private static Boolean VerifyPassword(String password, User user) - { - var pwdBytes = Encoding.UTF8.GetBytes(password); - var saltBytes = Encoding.UTF8.GetBytes(user.Salt + "innovEnergy"); - var pwdHash = Crypto.ComputeHash(pwdBytes, saltBytes); - - return user.Password == pwdHash; - } - } diff --git a/csharp/App/Backend/Controllers/Credentials.cs b/csharp/App/Backend/Controllers/Credentials.cs deleted file mode 100644 index f3149f7e8..000000000 --- a/csharp/App/Backend/Controllers/Credentials.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace InnovEnergy.App.Backend.Controllers; - -[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] -public record Credentials(String Username, String Password); \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Credentials.cs b/csharp/App/Backend/DataTypes/Credentials.cs new file mode 100644 index 000000000..d17c3f063 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Credentials.cs @@ -0,0 +1,6 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.App.Backend.DataTypes; + +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +public record Credentials(String Username, String Password); \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Folder.cs b/csharp/App/Backend/DataTypes/Folder.cs new file mode 100644 index 000000000..f383ef483 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Folder.cs @@ -0,0 +1,3 @@ +namespace InnovEnergy.App.Backend.DataTypes; + +public class Folder : TreeNode {} \ No newline at end of file diff --git a/csharp/App/Backend/Model/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs similarity index 80% rename from csharp/App/Backend/Model/Installation.cs rename to csharp/App/Backend/DataTypes/Installation.cs index b6c4b7968..c3f60e2e2 100644 --- a/csharp/App/Backend/Model/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.App.Backend.Model; +namespace InnovEnergy.App.Backend.DataTypes; public class Installation : TreeNode @@ -14,7 +14,6 @@ public class Installation : TreeNode public Double Long { get; set; } public String S3Bucket { get; set; } = ""; - public String? S3Key { get; set; } - -} + public String S3Key { get; set; } = ""; +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/Credentials.cs b/csharp/App/Backend/DataTypes/Methods/Credentials.cs new file mode 100644 index 000000000..06db0d78c --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Credentials.cs @@ -0,0 +1,25 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.Relations; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + +public static class CredentialsMethods +{ + public static Session? Login(this Credentials credentials) + { + if (credentials.Username.IsNull() || credentials.Password.IsNull()) + return null; + + var user = Db.GetUserByEmail(credentials.Username); + + if (user is null || !user.VerifyPassword(credentials.Password)) + return null; + + var session = new Session(user); + + return Db.Create(session) + ? session + : null; + } +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs new file mode 100644 index 000000000..274574a4f --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -0,0 +1,73 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + +public static class FolderMethods +{ + public static IEnumerable ChildFolders(this Folder parent) + { + return Db + .Folders + .Where(f => f.ParentId == parent.Id); + } + + public static IEnumerable ChildInstallations(this Folder parent) + { + return Db + .Installations + .Where(f => f.ParentId == parent.Id); + } + + public static IEnumerable DescendantFolders(this Folder parent) + { + return parent.Traverse(ChildFolders); + } + + public static Boolean IsDescendantOf(this Folder folder, Folder ancestor) + { + return folder + .Ancestors() + .Any(u => u.Id == ancestor.Id); + } + + public static IEnumerable Ancestors(this Folder folder) + { + return folder.Unfold(Parent); + } + + public static Folder? Parent(this Folder folder) + { + return IsAbsoluteRoot(folder) + ? null + : Db.GetFolderById(folder.ParentId); + } + + public static Boolean IsAbsoluteRoot(this Folder folder) + { + return folder.ParentId == 0; // root has ParentId 0 by definition + } + + public static Boolean IsRelativeRoot(this Folder folder) + { + return folder.ParentId < 0; // root has ParentId 0 by definition + } + + public static Boolean WasMoved(this Folder folder) + { + if (folder.IsRelativeRoot()) + return false; + + var existingFolder = Db.GetFolderById(folder.Id); + + return existingFolder is not null + && existingFolder.ParentId != folder.ParentId; + } + + public static Boolean Exists(this Folder folder) + { + return Db.Folders.Any(f => f.Id == folder.Id); + } + +} + diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs new file mode 100644 index 000000000..795e8a1dc --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -0,0 +1,47 @@ +using InnovEnergy.App.Backend.Database; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + + +public static class InstallationMethods +{ + public static IEnumerable Ancestors(this Installation installation) + { + var parentFolder = Parent(installation); + + return parentFolder is null + ? Enumerable.Empty() + : parentFolder.Ancestors(); + } + + public static Folder? Parent(this Installation installation) + { + return installation.IsRelativeRoot() + ? null + : Db.GetFolderById(installation.ParentId); + } + + public static Boolean IsRelativeRoot(this Installation i) + { + return i.ParentId < 0; + } + + public static Boolean WasMoved(this Installation installation) + { + if (installation.IsRelativeRoot()) + return false; + + var existingInstallation = Db.GetInstallationById(installation.Id); + + return existingInstallation is not null + && existingInstallation.ParentId != installation.ParentId; + } + + public static Boolean Exists(this Installation installation) + { + return Db.Installations.Any(i => i.Id == installation.Id); + } + + +} + diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs new file mode 100644 index 000000000..fc66f90d1 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -0,0 +1,119 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.Relations; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + +public static class SessionMethods +{ + public static Boolean Create(this Session? session, Folder? folder) + { + var user = session?.User; + + return user is not null + && folder is not null + && user.HasWriteAccess + && user.HasAccessTo(folder.Parent()) + && Db.Create(folder); + } + + public static Boolean Update(this Session? session, Folder? folder) + { + var user = session?.User; + + return user is not null + && folder is not null + && user.HasWriteAccess + && user.HasAccessTo(folder) + && (folder.IsRelativeRoot() || user.HasAccessTo(folder.Parent())) + && Db.Update(folder); + } + + public static Boolean Delete(this Session? session, Folder? folder) + { + var user = session?.User; + + return user is not null + && folder is not null + && user.HasWriteAccess + && user.HasAccessTo(folder) // TODO: && user.HasAccessTo(folder.Parent()) ??? + && Db.Delete(folder); + } + + + public static Boolean Create(this Session? session, Installation? installation) + { + var user = session?.User; + + return user is not null + && installation is not null + && user.HasWriteAccess + && user.HasAccessTo(installation.Parent()) + && Db.Create(installation); + } + + public static Boolean Update(this Session? session, Installation? installation) + { + var user = session?.User; + + return user is not null + && installation is not null + && user.HasWriteAccess + && installation.Exists() + && user.HasAccessTo(installation) + && (installation.IsRelativeRoot() || user.HasAccessTo(installation.Parent())) // TODO: triple check this + && Db.Update(installation); + } + + public static Boolean Delete(this Session? session, Installation? installation) + { + var user = session?.User; + + return user is not null + && installation is not null + && user.HasWriteAccess + && user.HasAccessTo(installation) // TODO: && user.HasAccessTo(installation.Parent()) ??? + && Db.Delete(installation); + } + + public static Boolean Create(this Session? session, User? newUser) + { + var sessionUser = session?.User; + + if (sessionUser is null || newUser is null || !sessionUser.HasWriteAccess) + return false; + + newUser.ParentId = sessionUser.Id; // Important! + + return Db.Create(newUser); + } + + public static Boolean Update(this Session? session, User? editedUser) + { + var sessionUser = session?.User; + + return sessionUser is not null + && editedUser is not null + && sessionUser.HasWriteAccess + && sessionUser.HasAccessTo(editedUser) + && (editedUser.IsRelativeRoot() || sessionUser.HasAccessTo(editedUser.Parent())) // TODO: triple check this + && Db.Update(editedUser); + } + + public static Boolean Delete(this Session? session, User? userToDelete) + { + var sessionUser = session?.User; + + return sessionUser is not null + && userToDelete is not null + && sessionUser.HasWriteAccess + && sessionUser.HasAccessTo(userToDelete) // TODO: && user.HasAccessTo(installation.Parent()) ??? + && Db.Delete(userToDelete); + } + + public static Boolean Logout(this Session? session) + { + return session is not null + && Db.Sessions.Delete(s => s.Token == session.Token) > 0; + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs new file mode 100644 index 000000000..99988f7e8 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -0,0 +1,342 @@ +using System.Net.Http.Headers; +using System.Net.Mail; +using System.Security.Cryptography; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using InnovEnergy.App.Backend.Database; +using InnovEnergy.Lib.Utils; +using Convert = System.Convert; +using static System.Text.Encoding; + + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + + +public static class UserMethods +{ + public static IEnumerable AccessibleInstallations(this User user) + { + var direct = user.DirectlyAccessibleInstallations(); + var fromFolders = user + .AccessibleFolders() + .SelectMany(u => u.ChildInstallations()); + + return direct + .Concat(fromFolders) + .Distinct(); + } + + public static IEnumerable AccessibleFolders(this User user) + { + return user + .DirectlyAccessibleFolders() + .SelectMany(f => f.DescendantFolders()) + .Distinct(); + + // Distinct because the user might have direct access + // to a child folder of a folder he has already access to + } + + public static IEnumerable AccessibleFoldersAndInstallations(this User user) + { + var folders = user.AccessibleFolders() as IEnumerable; + var installations = user.AccessibleInstallations(); + + return folders.Concat(installations); + } + + public static IEnumerable DirectlyAccessibleInstallations(this User user) + { + return Db + .User2Installation + .Where(r => r.UserId == user.Id) + .Select(r => r.InstallationId) + .Select(Db.GetInstallationById) + .NotNull() + .Do(i => i.ParentId = -1); // hide inaccessible parents from calling user + } + + public static IEnumerable DirectlyAccessibleFolders(this User user) + { + return Db + .User2Folder + .Where(r => r.UserId == user.Id) + .Select(r => r.FolderId) + .Select(Db.GetFolderById) + .NotNull() + .Do(i => i.ParentId = -1); // hide inaccessible parents from calling user; + } + + public static IEnumerable ChildUsers(this User parent) + { + return Db + .Users + .Where(f => f.ParentId == parent.Id); + } + + public static IEnumerable DescendantUsers(this User parent) + { + return parent.Traverse(ChildUsers); + } + + public static Boolean IsDescendantOf(this User user, User ancestor) + { + return user + .Ancestors() + .Any(u => u.Id == ancestor.Id); + } + + private static IEnumerable Ancestors(this User user) + { + return user.Unfold(Parent); + } + + public static Boolean VerifyPassword(this User user, String password) + { + return user.Password == user.SaltAndHashPassword(password); + } + + public static String SaltAndHashPassword(this User user, String password) + { + var dataToHash = $"{password}{user.Salt()}"; + + return dataToHash + .Apply(UTF8.GetBytes) + .Apply(SHA256.HashData) + .Apply(Convert.ToBase64String); + } + + public static User? Parent(this User u) + { + return u.IsAbsoluteRoot() + ? null + : Db.GetUserById(u.ParentId); + } + + public static Boolean IsAbsoluteRoot(this User u) + { + return u.ParentId == 0; + } + + public static Boolean IsRelativeRoot(this User u) + { + return u.ParentId < 0; + } + + public static Boolean HasDirectAccessTo(this User user, Folder folder) + { + return Db + .User2Folder + .Any(r => r.FolderId == folder.Id && r.UserId == user.Id); + } + + public static Boolean HasAccessTo(this User user, Folder? folder) + { + if (folder is null) + return false; + + return folder + .Ancestors() + .Any(user.HasDirectAccessTo); + } + + public static Boolean HasDirectAccessTo(this User user, Installation installation) + { + return Db + .User2Installation + .Any(r => r.InstallationId == installation.Id && r.UserId == user.Id); + } + + public static Boolean HasAccessTo(this User user, Installation? installation) + { + if (installation is null) + return false; + + return user.HasDirectAccessTo(installation) || + installation.Ancestors().Any(user.HasDirectAccessTo); + } + + public static Boolean HasAccessTo(this User user, User? other) + { + if (other is null) + return false; + + return other + .Ancestors() + .Skip(1) // Important! skip self, user cannot delete or edit himself + .Contains(user); + } + + public static String Salt(this User user) + { + // + id => salt unique per user + // + InnovEnergy => globally unique + + return $"{user.Id}InnovEnergy"; + } + + + private static Byte[] HmacSha256Digest(String message, String secret) + { + // var encoding = new UTF8Encoding(); + // var keyBytes = encoding.GetBytes(secret); + // var messageBytes = encoding.GetBytes(message); + // var cryptographer = new HMACSHA256(keyBytes); + // return cryptographer.ComputeHash(messageBytes); + + var keyBytes = UTF8.GetBytes(secret); + var messageBytes = UTF8.GetBytes(message); + + return HMACSHA256.HashData(keyBytes, messageBytes); + } + + private static String BuildSignature(String method, String path, String data, Int64 time, String secret) + { + var messageToSign = ""; + messageToSign += method + " /v2/" + path + "\n"; + messageToSign += data + "\n"; + + // query strings + messageToSign += "\n"; + // headers + messageToSign += "\n"; + + messageToSign += time; + + Console.WriteLine("Message to sign:\n" + messageToSign); + + var hmac = HmacSha256Digest(messageToSign, secret); + return Convert.ToBase64String(hmac); + } + + // public Object CreateAndSaveUserS3ApiKey(User user) + // { + // //EXOSCALE API URL + // const String url = "https://api-ch-dk-2.exoscale.com/v2/"; + // const String path = "access-key"; + // + // //TODO HIDE ME + // const String secret = "S2K1okphiCSNK4mzqr4swguFzngWAMb1OoSlZsJa9F0"; + // const String apiKey = "EXOb98ec9008e3ec16e19d7b593"; + // + // var installationList = User2Installation + // .Where(i => i.UserId == user.Id) + // .SelectMany(i => Installations.Where(f => i.InstallationId == f.Id)) + // .ToList(); + // + // + // var instList = new JsonArray(); + // + // foreach (var installation in installationList) + // { + // instList.Add(new JsonObject {["domain"] = "sos",["resource-name"] = installation.Name,["resource-type"] = "bucket"}); + // } + // + // var jsonPayload = new JsonObject { ["name"] = user.Email, ["operations"] = new JsonArray{ "list-sos-bucket", "get-sos-object" }, ["content"] = instList}; + // var stringPayload = jsonPayload.ToJsonString(); + // + // var unixExpiration = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60; + // var signature = BuildSignature("POST", path, stringPayload, unixExpiration , secret); + // + // var authHeader = "credential="+apiKey+",expires="+unixExpiration+",signature="+signature; + // + // var client = new HttpClient(); + // client.DefaultRequestHeaders.Authorization = + // new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authHeader); + // + // var content = new StringContent(stringPayload, Encoding.UTF8, "application/json"); + // + // + // var response = client.PostAsync(url+path, content).Result; + // + // if (response.StatusCode.ToString() != "OK") + // { + // return response; + // } + // + // var responseString = response.Content.ReadAsStringAsync().Result; + // return Enumerable.Last(Regex.Match(responseString, "key\\\":\\\"([A-Z])\\w+").ToString().Split('"')); + // // return SetUserS3ApiKey(user, newKey); + // + // } + + public static Object CreateAndSaveInstallationS3ApiKey(Installation installation) + { + //EXOSCALE API URL + const String url = "https://api-ch-dk-2.exoscale.com/v2/"; + const String path = "access-key"; + + //TODO HIDE ME + const String secret = "S2K1okphiCSNK4mzqr4swguFzngWAMb1OoSlZsJa9F0"; + const String apiKey = "EXOb98ec9008e3ec16e19d7b593"; + + + var jsonPayload = new JsonObject + { + ["name"] = installation.Id, + ["operations"] = new JsonArray + { + "list-sos-bucket", + "get-sos-object" + }, + ["content"] = new JsonArray + { + new JsonObject + { + ["domain"] = "sos", + ["resource-name"] = installation.Name, + ["resource-type"] = "bucket" + } + } + }; + + var stringPayload = jsonPayload.ToJsonString(); + + var unixExpiration = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60; + + var signature = BuildSignature("POST", path, stringPayload, unixExpiration, secret); + + var authHeader = "credential=" + apiKey + ",expires=" + unixExpiration + ",signature=" + signature; + + var client = new HttpClient(); + + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authHeader); + + var content = new StringContent(stringPayload, UTF8, "application/json"); + + var response = client.PostAsync(url + path, content).Result; + + if (response.StatusCode.ToString() != "OK") + { + return response; + } + + var responseString = response.Content.ReadAsStringAsync().Result; + var newKey = Regex + .Match(responseString, "key\\\":\\\"([A-Z])\\w+") + .ToString() + .Split('"') + .Last(); + + installation.S3Key = newKey; + Db.Update(installation); + return newKey; + } + + + + // TODO + private static Boolean IsValidEmail(String email) + { + try + { + var emailAddress = new MailAddress(email); + } + catch + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/TreeNode.Equality.cs b/csharp/App/Backend/DataTypes/TreeNode.Equality.cs new file mode 100644 index 000000000..e9c6767df --- /dev/null +++ b/csharp/App/Backend/DataTypes/TreeNode.Equality.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.App.Backend.DataTypes; + +public abstract partial class TreeNode +{ + public override Boolean Equals(Object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + + return Equals((TreeNode)obj); + } + + protected Boolean Equals(TreeNode other) => Id == other.Id; + + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] + public override Int32 GetHashCode() => Id.GetHashCode(); + + public static Boolean operator ==(TreeNode? left, TreeNode? right) => Equals(left, right); + public static Boolean operator !=(TreeNode? left, TreeNode? right) => !Equals(left, right); +} \ No newline at end of file diff --git a/csharp/App/Backend/Model/TreeNode.cs b/csharp/App/Backend/DataTypes/TreeNode.cs similarity index 67% rename from csharp/App/Backend/Model/TreeNode.cs rename to csharp/App/Backend/DataTypes/TreeNode.cs index d02890a62..de24da0b8 100644 --- a/csharp/App/Backend/Model/TreeNode.cs +++ b/csharp/App/Backend/DataTypes/TreeNode.cs @@ -1,6 +1,6 @@ using SQLite; -namespace InnovEnergy.App.Backend.Model; +namespace InnovEnergy.App.Backend.DataTypes; public abstract partial class TreeNode { @@ -12,10 +12,7 @@ public abstract partial class TreeNode [Indexed] // parent/child relation public Int64 ParentId { get; set; } - [Ignore] // not in DB, can be used in typescript as type discriminator + [Ignore] public String Type => GetType().Name; - [Ignore] - public IReadOnlyList? Children { get; set; } - } \ No newline at end of file diff --git a/csharp/App/Backend/Model/User.cs b/csharp/App/Backend/DataTypes/User.cs similarity index 75% rename from csharp/App/Backend/Model/User.cs rename to csharp/App/Backend/DataTypes/User.cs index 6e4e248ad..70ea9e034 100644 --- a/csharp/App/Backend/Model/User.cs +++ b/csharp/App/Backend/DataTypes/User.cs @@ -1,17 +1,14 @@ using SQLite; -namespace InnovEnergy.App.Backend.Model; +namespace InnovEnergy.App.Backend.DataTypes; public class User : TreeNode { [Indexed] public String Email { get; set; } = null!; public Boolean HasWriteAccess { get; set; } = false; - public String Salt { get; set; } = null!; public String Language { get; set; } = null!; public String Password { get; set; } = null!; // TODO: must reset pwd -} - - \ No newline at end of file +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Create.cs b/csharp/App/Backend/Database/Create.cs new file mode 100644 index 000000000..875b5ddef --- /dev/null +++ b/csharp/App/Backend/Database/Create.cs @@ -0,0 +1,38 @@ +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; + + +namespace InnovEnergy.App.Backend.Database; + + +public static partial class Db +{ + public static Boolean Create(Installation installation) + { + // SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion + return Connection.Insert(installation) > 0; + } + + public static Boolean Create(Folder folder) + { + return Connection.Insert(folder) > 0; + } + + public static Boolean Create(User user) + { + if (GetUserByEmail(user.Email) is not null) // TODO: User unique by username instead of email? + return false; + + user.Password = user.SaltAndHashPassword(user.Password); + + return Connection.Insert(user) > 0; + } + + + public static Boolean Create(Session session) + { + return Connection.Insert(session) > 0; + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index b2e488228..3e2fd815c 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -1,16 +1,20 @@ -using System.Diagnostics.CodeAnalysis; -using InnovEnergy.App.Backend.Model; -using InnovEnergy.App.Backend.Model.Relations; +using System.Reactive.Linq; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; using InnovEnergy.Lib.Utils; using SQLite; + + namespace InnovEnergy.App.Backend.Database; + public static partial class Db { internal const String DbPath = "./db.sqlite"; - public static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath); + private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath); public static TableQuery Sessions => Connection.Table(); public static TableQuery Folders => Connection.Table(); @@ -18,149 +22,55 @@ public static partial class Db public static TableQuery Users => Connection.Table(); public static TableQuery User2Folder => Connection.Table(); public static TableQuery User2Installation => Connection.Table(); - - public static Int32 NbUser2Installation => User2Installation.Count(); - public static Int32 NbUser2Folder => User2Folder.Count(); - public static Int32 NbFolders => Folders.Count(); - public static Int32 NbInstallations => Installations.Count(); - public static Int32 NbUsers => Users.Count(); - - - public static Folder? GetFolderById(Int64 id) - { - return Folders - .FirstOrDefault(f => f.Id == id); - } - - public static Installation? GetInstallationById(Int64 id) - { - return Installations - .FirstOrDefault(i => i.Id == id); - } - - public static User? GetUserById(Int64 id) - { - return Users - .FirstOrDefault(u => u.Id == id); - } - [SuppressMessage("ReSharper", "AccessToDisposedClosure")] static Db() { // on startup create/migrate tables - using var db = new SQLiteConnection(DbPath); - - db.RunInTransaction(() => + Connection.RunInTransaction(() => { - db.CreateTable(); - db.CreateTable(); - db.CreateTable(); - db.CreateTable(); - db.CreateTable(); - db.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); }); + + + var installation = Installations.First(); + UserMethods.CreateAndSaveInstallationS3ApiKey(installation); + + + Observable.Interval(TimeSpan.FromDays(1)) + .StartWith(0) // Do it right away (on startup) + .Subscribe(Cleanup); // and then daily } - - // the C in CRUD - private static Int64 Create(TreeNode treeNode) + + + private static Boolean RunTransaction(Func func) { + var savepoint = Connection.SaveTransactionPoint(); + var success = false; + try { - Connection.Insert(treeNode); - return SQLite3.LastInsertRowid(Connection.Handle); + success = func(); } - catch (Exception e) + finally { - return -1; + if (success) + Connection.Release(savepoint); + else + Connection.RollbackTo(savepoint); } + + return success; } - - private static Boolean Create(Session session) - { - try - { - Connection.Insert(session); - return true; - } - catch (Exception e) - { - return false; - } - } - - // the U in CRUD - private static Boolean Update(TreeNode treeNode) - { - try - { - Connection.InsertOrReplace(treeNode); - return true; - } - catch (Exception e) - { - return false; - } - } - - // the D in CRUD - private static Boolean Delete(TreeNode treeNode) - { - try - { - Connection.Delete(treeNode); - return true; - } - catch (Exception e) - { - return false; - } - } - - public static IEnumerable GetAllAccessibleInstallations(User user) - { - var direct = GetDirectlyAccessibleInstallations(user); - var fromFolders = GetAllAccessibleFolders(user) - .SelectMany(GetChildInstallations); - - return direct - .Concat(fromFolders) - .Distinct(); - } - - public static IEnumerable GetAllAccessibleFolders(User user) - { - return GetDirectlyAccessibleFolders(user) - .SelectMany(GetDescendantFolders) - .Distinct(); - - // Distinct because the user might have direct access - // to a child folder of a folder he has already access to - } - - - public static IEnumerable GetDirectlyAccessibleInstallations(User user) - { - return User2Installation - .Where(r => r.UserId == user.Id) - .Select(r => r.InstallationId) - .Select(GetInstallationById) - .NotNull() - .Do(i => i.ParentId = 0); // hide inaccessible parents from calling user - } - - public static IEnumerable GetDirectlyAccessibleFolders(User user) - { - return User2Folder - .Where(r => r.UserId == user.Id) - .Select(r => r.FolderId) - .Select(GetFolderById) - .NotNull() - .Do(i => i.ParentId = 0); // hide inaccessible parents from calling user; - } + public static Boolean AddToAccessibleInstallations(Int64 userId, Int64 updatedInstallationId) { @@ -201,45 +111,22 @@ public static partial class Db } - public static User? GetUserByToken(String token) + private static void Cleanup(Int64 _) { - return Sessions - .Where(s => s.Token == token).ToList() - .Where(s => s.Valid) - .Select(s => s.UserId) - .Select(GetUserById) - .FirstOrDefault(); + DeleteS3Keys(); + DeleteStaleSessions(); } - - public static Boolean NewSession(Session ses) => Create(ses); - - public static Boolean DeleteSession(Int64 id) + private static void DeleteStaleSessions() { - try - { - Sessions.Delete(u => u.UserId == id); - return true; - } - catch (Exception e) - { - return false; - } + var deadline = DateTime.Now - Session.MaxAge; + Sessions.Delete(s => s.LastSeen < deadline); } - public static String? GetInstallationS3Key(Int64 installationId) + private static void DeleteS3Keys() { - return Installations - .FirstOrDefault(i => i.Id == installationId)? - .S3Key; - } - - public static void DeleteAllS3Keys() - { - foreach (var installation in Installations.ToList()) - { - installation.S3Key = null; - Update(installation); - } + void DeleteKeys() => Installations.Do(i => i.S3Key = "").ForEach(Update); // TODO + + Connection.RunInTransaction(DeleteKeys); } } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Delete.cs b/csharp/App/Backend/Database/Delete.cs new file mode 100644 index 000000000..0e495afde --- /dev/null +++ b/csharp/App/Backend/Database/Delete.cs @@ -0,0 +1,63 @@ +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; + + +namespace InnovEnergy.App.Backend.Database; + + +public static partial class Db +{ + public static Boolean Delete(Folder folder) + { + return RunTransaction(DeleteFolderAndAllItsDependencies); + + Boolean DeleteFolderAndAllItsDependencies() + { + return folder + .DescendantFolders() + .All(DeleteDescendantFolderAndItsDependencies); + } + + Boolean DeleteDescendantFolderAndItsDependencies(Folder f) + { + User2Folder .Delete(r => r.FolderId == f.Id); + Installations.Delete(r => r.ParentId == f.Id); + + return Folders.Delete(r => r.Id == f.Id) > 0; + } + } + + public static Boolean Delete(Installation installation) + { + return RunTransaction(DeleteInstallationAndItsDependencies); + + Boolean DeleteInstallationAndItsDependencies() + { + User2Installation.Delete(i => i.InstallationId == installation.Id); + return Installations.Delete(i => i.Id == installation.Id) > 0; + } + } + + public static Boolean Delete(User user) + { + return RunTransaction(DeleteUserAndHisDependencies); + + Boolean DeleteUserAndHisDependencies() + { + User2Folder .Delete(u => u.UserId == user.Id); + User2Installation.Delete(u => u.UserId == user.Id); + + return Users.Delete(u => u.Id == user.Id) > 0; + } + } + + + #pragma warning disable CS0618 + + // private!! + private static Boolean Delete(Session session) + { + return Sessions.Delete(s => s.Id == session.Id) > 0; + } +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Fake.cs b/csharp/App/Backend/Database/Fake.cs index 7eca66ce8..ab144c11d 100644 --- a/csharp/App/Backend/Database/Fake.cs +++ b/csharp/App/Backend/Database/Fake.cs @@ -1,4 +1,4 @@ -using InnovEnergy.App.Backend.Model.Relations; +using InnovEnergy.App.Backend.Relations; namespace InnovEnergy.App.Backend.Database; @@ -18,7 +18,7 @@ public static partial class Db private static void CreateFakeUserTree() { - foreach (var userId in Enumerable.Range(1, NbUsers)) + foreach (var userId in Enumerable.Range(1, Users.Count())) { var user = GetUserById(userId); if (user is null) @@ -34,7 +34,7 @@ public static partial class Db private static void CreateFakeFolderTree() { - foreach (var folderId in Enumerable.Range(1, NbFolders)) + foreach (var folderId in Enumerable.Range(1, Folders.Count())) { var folder = GetFolderById(folderId); if (folder is null) @@ -50,7 +50,7 @@ public static partial class Db private static void LinkFakeInstallationsToFolders() { - var nFolders = NbFolders; + var nFolders = Folders.Count(); foreach (var installation in Installations) { @@ -64,8 +64,8 @@ public static partial class Db foreach (var uf in User2Folder) // remove existing relations Connection.Delete(uf); - var nFolders = NbFolders; - var nUsers = NbUsers; + var nFolders = Folders.Count(); + var nUsers = Users.Count(); foreach (var user in Users) while (Random.Shared.Next((Int32)(nUsers - user.Id + 1)) != 0) @@ -84,7 +84,7 @@ public static partial class Db foreach (var ui in User2Installation) // remove existing relations Connection.Delete(ui); - var nbInstallations = NbInstallations; + var nbInstallations = Installations.Count(); foreach (var user in Users) while (Random.Shared.Next(5) != 0) diff --git a/csharp/App/Backend/Database/Folder.cs b/csharp/App/Backend/Database/Folder.cs deleted file mode 100644 index 9383354c2..000000000 --- a/csharp/App/Backend/Database/Folder.cs +++ /dev/null @@ -1,91 +0,0 @@ -using InnovEnergy.App.Backend.Model; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.App.Backend.Database; - -public static partial class Db -{ - public static IEnumerable GetChildFolders(this Folder parent) - { - return Folders.Where(f => f.ParentId == parent.Id); - } - - public static IEnumerable GetChildInstallations(this Folder parent) - { - return Installations.Where(f => f.ParentId == parent.Id); - } - - public static IEnumerable GetDescendantFolders(this Folder parent) - { - return parent.Traverse(GetChildFolders); - } - - public static Boolean IsDescendantOf(this Folder folder, Int64 ancestorFolderId) - { - return Ancestors(folder) - .Any(u => u.Id == ancestorFolderId); - } - - public static Boolean IsDescendantOf(this Folder folder, Folder ancestor) - { - return IsDescendantOf(folder, ancestor.Id); - } - - private static IEnumerable Ancestors(this Folder child) - { - return child.Unfold(GetParent); - } - - public static Folder? GetParent(this Folder f) - { - return IsRoot(f) - ? null - : GetFolderById(f.ParentId); - } - - public static Boolean IsRoot(this Folder f) - { - return f.ParentId == 0; // root has ParentId 0 by definition - } - - public static Int64 CreateFolder(Folder folder) - { - return Create(folder); - } - - public static Boolean UpdateFolder(Folder folder) - { - // TODO: no circles in path - - return Update(folder); - } - - // These should not be necessary, just Update folder/installation with new parentId - - // public Boolean ChangeParent(Installation child, Int64 parentId) - // { - // child.ParentId = parentId; - // return UpdateInstallation(child); - // } - // - // public Boolean ChangeParent(Folder child, Int64 parentId) - // { - // child.ParentId = parentId; - // return UpdateFolder(child); - // } - - public static Boolean DeleteFolder(Folder folder) - { - // Delete direct children - User2Folder .Delete(f => f.FolderId == folder.Id); - Installations.Delete(i => i.ParentId == folder.Id); - - // recursion - Folders.Where(f => f.ParentId == folder.Id) - .ForEach(DeleteFolder); - - return Delete(folder); - } - -} - diff --git a/csharp/App/Backend/Database/Installation.cs b/csharp/App/Backend/Database/Installation.cs deleted file mode 100644 index 3220637c0..000000000 --- a/csharp/App/Backend/Database/Installation.cs +++ /dev/null @@ -1,47 +0,0 @@ -using InnovEnergy.App.Backend.Model; -using SQLite; - -namespace InnovEnergy.App.Backend.Database; - -public static partial class Db -{ - public static IEnumerable Ancestors(this Installation installation) - { - var parentFolder = GetParent(installation); - - return parentFolder is null - ? Enumerable.Empty() - : Ancestors(parentFolder); - } - - public static Folder? GetParent(this Installation installation) - { - return IsRoot(installation) - ? null - : GetFolderById(installation.ParentId); - } - - public static Boolean IsRoot(this Installation i) - { - return i.ParentId == 0; // root has ParentId 0 by definition - } - - public static Int64 CreateInstallation(this Installation installation) - { - return Create(installation); - } - - public static Boolean UpdateInstallation(this Installation installation) - { - return Update(installation); - } - - public static Boolean DeleteInstallation(this Installation installation) - { - User2Installation.Delete(i => i.InstallationId == installation.Id); - - return Delete(installation); - } - -} - diff --git a/csharp/App/Backend/Database/Read.cs b/csharp/App/Backend/Database/Read.cs new file mode 100644 index 000000000..312d0c94d --- /dev/null +++ b/csharp/App/Backend/Database/Read.cs @@ -0,0 +1,86 @@ +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.Relations; + + +namespace InnovEnergy.App.Backend.Database; + + +public static partial class Db +{ + public static Folder? GetFolderById(Int64 id) + { + return Folders + .FirstOrDefault(f => f.Id == id); + } + + public static Installation? GetInstallationById(Int64 id) + { + return Installations + .FirstOrDefault(i => i.Id == id); + } + + public static User? GetUserById(Int64 id) + { + return Users + .FirstOrDefault(u => u.Id == id); + } + + // private!! + private static Session? GetSessionById(Int64 id) + { + #pragma warning disable CS0618 + + return Sessions + .FirstOrDefault(u => u.Id == id); + + #pragma warning restore CS0618 + } + + + public static User? GetUserByEmail(String email) + { + return Users + .FirstOrDefault(u => u.Email == email); + } + + public static Session? GetSession(String token) + { + var session = Sessions + .FirstOrDefault(s => s.Token == token); + + // cannot use session.Valid in the DB query above. + // It does not exist in the db (IgnoreAttribute) + + if (session is null) + return null; + + if (!session.Valid) + { + Delete(session); + return null; + } + + return session; + } + + public static User? GetUserBySessionToken(String token) + { + var session = Sessions + .FirstOrDefault(s => s.Token == token); + + // cannot user session.Expired in the DB query above. + // It does not exist in the db (IgnoreAttribute) + + if (session is null) + return null; + + if (!session.Valid) + { + Delete(session); + return null; + } + + return GetUserById(session.UserId); + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Update.cs b/csharp/App/Backend/Database/Update.cs new file mode 100644 index 000000000..e552df33f --- /dev/null +++ b/csharp/App/Backend/Database/Update.cs @@ -0,0 +1,64 @@ +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; + + +namespace InnovEnergy.App.Backend.Database; + + +public static partial class Db +{ + public static Boolean Update(Folder folder) + { + if (folder.IsRelativeRoot()) // TODO: triple check + { + var original = GetFolderById(folder.Id); + if (original is null) + return false; + + folder.ParentId = original.ParentId; + } + + return Connection.InsertOrReplace(folder) > 0; + } + + public static Boolean Update(Installation installation) + { + if (installation.IsRelativeRoot()) // TODO: triple check + { + var original = GetInstallationById(installation.Id); + if (original is null) + return false; + + installation.ParentId = original.ParentId; + } + + return Connection.InsertOrReplace(installation) > 0; + } + + + public static Boolean Update(User user) + { + var originalUser = GetUserById(user.Id); + + return originalUser is not null + && user.Id == originalUser.Id // these columns must not be modified! + && user.ParentId == originalUser.ParentId + && user.Email == originalUser.Email + && user.Password == originalUser.Password + && Connection.InsertOrReplace(user) > 0; + } + + public static Boolean Update(this Session session) + { + #pragma warning disable CS0618 + var originalSession = GetSessionById(session.Id); + #pragma warning restore CS0618 + + return originalSession is not null + && session.Token == originalSession.Token // these columns must not be modified! + && session.UserId == originalSession.UserId + && Connection.InsertOrReplace(session) > 0; + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/User.cs b/csharp/App/Backend/Database/User.cs deleted file mode 100644 index 5d91f3eee..000000000 --- a/csharp/App/Backend/Database/User.cs +++ /dev/null @@ -1,254 +0,0 @@ -using System.Net.Http.Headers; -using System.Net.Mail; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json.Nodes; -using System.Text.RegularExpressions; -using InnovEnergy.App.Backend.Model; -using InnovEnergy.App.Backend.Utils; -using InnovEnergy.Lib.Utils; - - -namespace InnovEnergy.App.Backend.Database; - -public static partial class Db -{ - public static IEnumerable GetChildUsers(this User parent) - { - return Users - .Where(f => f.ParentId == parent.Id); - } - - public static IEnumerable GetDescendantUsers(this User parent) - { - return parent.Traverse(GetChildUsers); - } - - public static Boolean IsDescendantOf(this User user, User ancestor) - { - return Ancestors(user) - .Any(u => u.Id == ancestor.Id); - } - - private static IEnumerable Ancestors(this User child) - { - return child.Unfold(GetParent); - } - - public static User? GetParent(this User u) - { - return IsRoot(u) - ? null - : GetUserById(u.ParentId); - } - - public static Boolean IsRoot(this User u) - { - return u.ParentId == 0; // root has ParentId 0 by definition - } - - public static Boolean HasDirectAccessToFolder(this User user, Folder folder) - { - return HasDirectAccessToFolder(user, folder.Id); - } - - public static Boolean HasDirectAccessToFolder(this User user, Int64 folderId) - { - return User2Folder.Any(r => r.FolderId == folderId && r.UserId == user.Id); - } - - public static Boolean HasAccessToFolder(this User user, Int64 folderId) - { - var folder = GetFolderById(folderId); - if (folder is null) - return false; - - return Ancestors(folder).Any(f => HasDirectAccessToFolder(user, f)); - } - - public static User? GetUserByEmail(String email) => Users.FirstOrDefault(u => u.Email == email); - - public static Int64 CreateUser(User user) - { - if (GetUserByEmail(user.Email) is not null) - return -1; // TODO: User with that email already exists - - //Salting and Hashing password - var salt = Crypto.GenerateSalt(); - var hashedPassword = Crypto.ComputeHash(Encoding.UTF8.GetBytes(user.Password), - Encoding.UTF8.GetBytes(salt + "innovEnergy")); - - user.Salt = salt; - user.Password = hashedPassword; - - return Create(user); - } - - - private static Byte[] HmacSha256Digest(String message, String secret) - { - var encoding = new UTF8Encoding(); - var keyBytes = encoding.GetBytes(secret); - var messageBytes = encoding.GetBytes(message); - var cryptographer = new HMACSHA256(keyBytes); - - return cryptographer.ComputeHash(messageBytes); - } - - private static String BuildSignature(String method, String path, String data, Int64 time, String secret) - { - var messageToSign = ""; - messageToSign += method + " /v2/" + path + "\n"; - messageToSign += data + "\n"; - - // query strings - messageToSign += "\n"; - // headers - messageToSign += "\n"; - - messageToSign += time; - - Console.WriteLine("Message to sign:\n" + messageToSign); - - var hmac = HmacSha256Digest(messageToSign, secret); - return Convert.ToBase64String(hmac); - } - - // public Object CreateAndSaveUserS3ApiKey(User user) - // { - // //EXOSCALE API URL - // const String url = "https://api-ch-dk-2.exoscale.com/v2/"; - // const String path = "access-key"; - // - // //TODO HIDE ME - // const String secret = "S2K1okphiCSNK4mzqr4swguFzngWAMb1OoSlZsJa9F0"; - // const String apiKey = "EXOb98ec9008e3ec16e19d7b593"; - // - // var installationList = User2Installation - // .Where(i => i.UserId == user.Id) - // .SelectMany(i => Installations.Where(f => i.InstallationId == f.Id)) - // .ToList(); - // - // - // var instList = new JsonArray(); - // - // foreach (var installation in installationList) - // { - // instList.Add(new JsonObject {["domain"] = "sos",["resource-name"] = installation.Name,["resource-type"] = "bucket"}); - // } - // - // var jsonPayload = new JsonObject { ["name"] = user.Email, ["operations"] = new JsonArray{ "list-sos-bucket", "get-sos-object" }, ["content"] = instList}; - // var stringPayload = jsonPayload.ToJsonString(); - // - // var unixExpiration = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60; - // var signature = BuildSignature("POST", path, stringPayload, unixExpiration , secret); - // - // var authHeader = "credential="+apiKey+",expires="+unixExpiration+",signature="+signature; - // - // var client = new HttpClient(); - // client.DefaultRequestHeaders.Authorization = - // new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authHeader); - // - // var content = new StringContent(stringPayload, Encoding.UTF8, "application/json"); - // - // - // var response = client.PostAsync(url+path, content).Result; - // - // if (response.StatusCode.ToString() != "OK") - // { - // return response; - // } - // - // var responseString = response.Content.ReadAsStringAsync().Result; - // return Enumerable.Last(Regex.Match(responseString, "key\\\":\\\"([A-Z])\\w+").ToString().Split('"')); - // // return SetUserS3ApiKey(user, newKey); - // - // } - - public static Object CreateAndSaveInstallationS3ApiKey(Installation installation) - { - //EXOSCALE API URL - const String url = "https://api-ch-dk-2.exoscale.com/v2/"; - const String path = "access-key"; - - //TODO HIDE ME - const String secret = "S2K1okphiCSNK4mzqr4swguFzngWAMb1OoSlZsJa9F0"; - const String apiKey = "EXOb98ec9008e3ec16e19d7b593"; - - var instList = new JsonArray(); - instList.Add(new JsonObject {["domain"] = "sos",["resource-name"] = installation.Name,["resource-type"] = "bucket"}); - - var jsonPayload = new JsonObject { ["name"] = installation.Id, ["operations"] = new JsonArray{ "list-sos-bucket", "get-sos-object" }, ["content"] = instList}; - var stringPayload = jsonPayload.ToJsonString(); - - var unixExpiration = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60; - var signature = BuildSignature("POST", path, stringPayload, unixExpiration , secret); - - var authHeader = "credential="+apiKey+",expires="+unixExpiration+",signature="+signature; - - var client = new HttpClient(); - client.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authHeader); - - var content = new StringContent(stringPayload, Encoding.UTF8, "application/json"); - - - var response = client.PostAsync(url+path, content).Result; - - if (response.StatusCode.ToString() != "OK") - { - return response; - } - - var responseString = response.Content.ReadAsStringAsync().Result; - var newKey = Enumerable.Last(Regex.Match(responseString, "key\\\":\\\"([A-Z])\\w+").ToString().Split('"')); - - installation.S3Key = newKey; - UpdateInstallation(installation); - return newKey; - } - - public static Boolean UpdateUser(User user) - { - var oldUser = GetUserById(user.Id); - if (oldUser == null) - return false; // TODO: "User doesn't exist" - - //Checking for unchangeable things - // TODO: depends on privileges of caller - - user.Id = oldUser.Id; - user.ParentId = oldUser.ParentId; - user.Email = oldUser.Email; - - return Update(user); - } - - public static Boolean DeleteUser(User user) - { - User2Folder.Delete(u => u.UserId == user.Id); - User2Installation.Delete(u => u.UserId == user.Id); - - //Todo check for orphaned Installations/Folders - - // GetChildUsers() - - return Delete(user); - } - - - // TODO - private static Boolean IsValidEmail(String email) - { - try - { - var emailAddress = new MailAddress(email); - } - catch - { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/csharp/App/Backend/Model/Folder.cs b/csharp/App/Backend/Model/Folder.cs deleted file mode 100644 index 041c0f479..000000000 --- a/csharp/App/Backend/Model/Folder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace InnovEnergy.App.Backend.Model; - -public class Folder : TreeNode -{ - -} \ No newline at end of file diff --git a/csharp/App/Backend/Model/Relations/Session.cs b/csharp/App/Backend/Model/Relations/Session.cs deleted file mode 100644 index 829cc9a60..000000000 --- a/csharp/App/Backend/Model/Relations/Session.cs +++ /dev/null @@ -1,35 +0,0 @@ -using SQLite; - -namespace InnovEnergy.App.Backend.Model.Relations; - -public class Session : Relation -{ - [Indexed] public String Token { get => Left ; set => Left = value;} - [Indexed] public Int64 UserId { get => Right; set => Right = value;} - [Indexed] public DateTime ExpiresAt { get; set; } - - [Ignore] public Boolean Valid => ExpiresAt > DateTime.Now; - [Ignore] public Boolean Expired => !Valid; - - [Obsolete("To be used only by serializer")] - public Session() - {} - - public Session(User user) : this(user, TimeSpan.FromDays(7)) - { - } - - public Session(User user, TimeSpan validFor) - { - Token = CreateToken(); - UserId = user.Id; - ExpiresAt = DateTime.Now + validFor; - } - - private static String CreateToken() - { - var token = new Byte[16]; // 128 bit - Random.Shared.NextBytes(token); - return Convert.ToBase64String(token); - } -} \ No newline at end of file diff --git a/csharp/App/Backend/Model/Relations/User2Folder.cs b/csharp/App/Backend/Model/Relations/User2Folder.cs deleted file mode 100644 index f93cab568..000000000 --- a/csharp/App/Backend/Model/Relations/User2Folder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using SQLite; - -namespace InnovEnergy.App.Backend.Model.Relations; - -internal class User2Folder : Relation -{ - [Indexed] public Int64 UserId { get => Left ; set => Left = value;} - [Indexed] public Int64 FolderId { get => Right; set => Right = value;} -} \ No newline at end of file diff --git a/csharp/App/Backend/Model/Relations/User2Installation.cs b/csharp/App/Backend/Model/Relations/User2Installation.cs deleted file mode 100644 index fcd2760dd..000000000 --- a/csharp/App/Backend/Model/Relations/User2Installation.cs +++ /dev/null @@ -1,9 +0,0 @@ -using SQLite; - -namespace InnovEnergy.App.Backend.Model.Relations; - -internal class User2Installation : Relation -{ - [Indexed] public Int64 UserId { get => Left ; set => Left = value;} - [Indexed] public Int64 InstallationId { get => Right; set => Right = value;} -} \ No newline at end of file diff --git a/csharp/App/Backend/Model/TreeNode.Equality.cs b/csharp/App/Backend/Model/TreeNode.Equality.cs deleted file mode 100644 index f37cd51d7..000000000 --- a/csharp/App/Backend/Model/TreeNode.Equality.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace InnovEnergy.App.Backend.Model; - -public abstract partial class TreeNode -{ - // Note: Only consider Id, but not ParentId for TreeNode equality checks - protected Boolean Equals(TreeNode other) - { - return Id == other.Id; - } - - public override Boolean Equals(Object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((TreeNode)obj); - } - - [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] - public override Int32 GetHashCode() => Id.GetHashCode(); -} \ No newline at end of file diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 7b6df34b9..d49fa856c 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -8,11 +8,9 @@ public static class Program { public static void Main(String[] args) { - using (var db = Db.Connect()) - db.CreateFakeRelations(); - - Observable.Interval(TimeSpan.FromDays(1)).Subscribe((_) => deleteInstallationS3KeysDaily()); + Db.CreateFakeRelations(); + var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // TODO: remove magic, specify controllers explicitly @@ -47,22 +45,19 @@ public static class Program app.Run(); } - private static void deleteInstallationS3KeysDaily() - { - using var db = Db.Connect(); - db.DeleteS3KeysDaily(); - - } + private static async Task SetSessionUser(HttpContext ctx, RequestDelegate next) { var headers = ctx.Request.Headers; - var hasToken = headers.TryGetValue("auth", out var token); + var hasToken = headers.TryGetValue("auth", out var token) ; if (hasToken) { - using var db = Db.Connect(); - ctx.Items["User"] = db.GetUserByToken(token.ToString()); + var session = Db.GetSession(token); + + if (session is not null) + ctx.Items["User"] = session; } await next(ctx); diff --git a/csharp/App/Backend/Model/Relations/Relation.cs b/csharp/App/Backend/Relations/Relation.cs similarity index 95% rename from csharp/App/Backend/Model/Relations/Relation.cs rename to csharp/App/Backend/Relations/Relation.cs index a791a4609..7e37b87ab 100644 --- a/csharp/App/Backend/Model/Relations/Relation.cs +++ b/csharp/App/Backend/Relations/Relation.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using SQLite; -namespace InnovEnergy.App.Backend.Model.Relations; +namespace InnovEnergy.App.Backend.Relations; public abstract class Relation { diff --git a/csharp/App/Backend/Relations/Session.cs b/csharp/App/Backend/Relations/Session.cs new file mode 100644 index 000000000..016ffa0ce --- /dev/null +++ b/csharp/App/Backend/Relations/Session.cs @@ -0,0 +1,44 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.Lib.Utils; +using SQLite; + +namespace InnovEnergy.App.Backend.Relations; + +public class Session : Relation +{ + public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(7); + + [Unique ] public String Token { get => Left ; init => Left = value;} + [Indexed] public Int64 UserId { get => Right; init => Right = value;} + [Indexed] public DateTime LastSeen { get; set; } + + [Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge + && !User.IsNull(); + + [Ignore] public User User => _User ??= Db.GetUserById(UserId)!; + + + private User? _User; + + [Obsolete("To be used only by deserializer")] + public Session() + {} + + public Session(User user) + { + _User = user; + Token = CreateToken(); + UserId = user.Id; + LastSeen = DateTime.Now; + } + + private static String CreateToken() + { + var token = new Byte[24]; + Random.Shared.NextBytes(token); + return Convert.ToBase64String(token); + } + + +} \ No newline at end of file diff --git a/csharp/App/Backend/Relations/User2Folder.cs b/csharp/App/Backend/Relations/User2Folder.cs new file mode 100644 index 000000000..0cd1fa191 --- /dev/null +++ b/csharp/App/Backend/Relations/User2Folder.cs @@ -0,0 +1,9 @@ +using SQLite; + +namespace InnovEnergy.App.Backend.Relations; + +public class User2Folder : Relation +{ + [Indexed] public Int64 UserId { get => Left ; init => Left = value;} + [Indexed] public Int64 FolderId { get => Right; init => Right = value;} +} \ No newline at end of file diff --git a/csharp/App/Backend/Relations/User2Installation.cs b/csharp/App/Backend/Relations/User2Installation.cs new file mode 100644 index 000000000..f898253db --- /dev/null +++ b/csharp/App/Backend/Relations/User2Installation.cs @@ -0,0 +1,9 @@ +using SQLite; + +namespace InnovEnergy.App.Backend.Relations; + +public class User2Installation : Relation +{ + [Indexed] public Int64 UserId { get => Left ; init => Left = value;} + [Indexed] public Int64 InstallationId { get => Right; init => Right = value;} +} \ No newline at end of file diff --git a/csharp/App/Backend/Utils/Crypto.cs b/csharp/App/Backend/Utils/Crypto.cs deleted file mode 100644 index c176fd284..000000000 --- a/csharp/App/Backend/Utils/Crypto.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Security.Cryptography; - -namespace InnovEnergy.App.Backend.Utils; - -public static class Crypto -{ - public static String ComputeHash(Byte[] bytesToHash, Byte[] salt) - { - using var mySHA256 = SHA256.Create(); - var hashValue = mySHA256.ComputeHash(bytesToHash); - // var hashValue = new Rfc2898DeriveBytes(hashValue, salt, 10000); - return Convert.ToBase64String(hashValue); - } - - public static String GenerateSalt() - { - var bytes = new Byte[128 / 8]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(bytes); - return Convert.ToBase64String(bytes); - } -} \ No newline at end of file diff --git a/csharp/App/Backend/db.sqlite b/csharp/App/Backend/db.sqlite index 1d3664c4c..2522ae1e7 100644 Binary files a/csharp/App/Backend/db.sqlite and b/csharp/App/Backend/db.sqlite differ