diff --git a/csharp/App/Backend/Controllers/Controller.cs b/csharp/App/Backend/Controllers/Controller.cs index 46d6d67ad..d9228f964 100644 --- a/csharp/App/Backend/Controllers/Controller.cs +++ b/csharp/App/Backend/Controllers/Controller.cs @@ -2,388 +2,337 @@ using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes.Methods; using InnovEnergy.App.Backend.Relations; +using InnovEnergy.Lib.Utils; using Microsoft.AspNetCore.Mvc; -using static System.Net.HttpStatusCode; -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; +using Token = String; + [ApiController] [Route("api/")] -public class Controller +public class Controller : ControllerBase { - 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(Unauthorized)] - [Returns(BadRequest)] - [HttpPost($"{nameof(Login)}")] - public Object Login(Credentials credentials) + [HttpPost(nameof(Login))] + public ActionResult Login(String username, String password) { - var session = credentials.Login(); + var user = Db.GetUserByEmail(username); - return session is null - ? _Unauthorized - : session; + if (user is null || !user.VerifyPassword(password)) + return Unauthorized(); + + var session = new Session(user); + + return Db.Create(session) + ? session + : Unauthorized(); } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(Logout)}")] - public Object Logout() + [HttpPost(nameof(Logout))] + public ActionResult Logout(Token authToken) { - var session = GetSession(); + var session = Db.GetSession(authToken); return session.Logout() - ? _Ok - : _Unauthorized; + ? Ok() + : Unauthorized(); } - [Returns] - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetUserById)}")] - public Object GetUserById(Int64 id) + [HttpGet(nameof(GetUserById))] + public ActionResult GetUserById(Int64 id, Token authToken) { - var caller = GetSession()?.User; - if (caller == null) - return _Unauthorized; + var session = Db.GetSession(authToken)?.User; + if (session == null) + return Unauthorized(); var user = Db.GetUserById(id); - if (user is null || !caller.HasAccessTo(user)) - return _Unauthorized; + if (user is null || !session.HasAccessTo(user)) + return Unauthorized(); user.Password = ""; return user; } - [Returns] - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetInstallationById)}")] - public Object GetInstallationById(Int64 id) + [HttpGet(nameof(GetInstallationById))] + public ActionResult GetInstallationById(Int64 id, Token authToken) { - var user = GetSession()?.User; + var user = Db.GetSession(authToken)?.User; if (user == null) - return _Unauthorized; + return Unauthorized(); var installation = Db.GetInstallationById(id); if (installation is null || !user.HasAccessTo(installation)) - return _Unauthorized; + return Unauthorized(); return installation; } - [Returns] - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetUsersWithAccessToInstallation)}")] - public Object GetUsersWithAccessToInstallation(Int64 id) + [HttpGet(nameof(GetUsersWithAccessToInstallation))] + public ActionResult> GetUsersWithAccessToInstallation(Int64 id, Token authToken) { - var user = GetSession()?.User; + var user = Db.GetSession(authToken)?.User; if (user == null) - return _Unauthorized; + return Unauthorized(); var installation = Db.GetInstallationById(id); if (installation is null || !user.HasAccessTo(installation)) - return _Unauthorized; + return Unauthorized(); - var usersWithInheritedAccess = installation - .Ancestors() - .SelectMany(f => f.UsersWithDirectAccess() - .Where(u => u.IsDescendantOf(user)) - .Select(u => new { folderId = f.Id, user = u })) - .OfType(); + var directAccess = installation + .UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)); - var usersWithDirectAccess = installation.UsersWithDirectAccess() - .Where(u => u.IsDescendantOf(user)) - .Select(u => new { installationId = installation.Id, user = u }) - .OfType(); - - return usersWithInheritedAccess.Concat(usersWithDirectAccess); + var inheritedAccess = installation + .Ancestors() + .SelectMany(f => f.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { folderId = f.Id, user = u })); + + return directAccess + .Concat(inheritedAccess) + .Apply(Ok); // TODO: typing } - [Returns] - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetUsersWithAccessToFolder)}")] - public Object GetUsersWithAccessToFolder(Int64 id) + [HttpGet(nameof(GetUsersWithAccessToFolder))] + public ActionResult> GetUsersWithAccessToFolder(Int64 id, Token authToken) { - var user = GetSession()?.User; + var user = Db.GetSession(authToken)?.User; if (user == null) - return _Unauthorized; + return Unauthorized(); var folder = Db.GetFolderById(id); if (folder is null || !user.HasAccessTo(folder)) - return _Unauthorized; + return Unauthorized(); return folder - .Ancestors() - .Append(folder) - .SelectMany(f => f.UsersWithDirectAccess() - .Where(u => u.IsDescendantOf(user)) - .Select(u => new { folderId = f.Id, user = u })); + .Ancestors() + .Prepend(folder) + .SelectMany(f => f.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { folderId = f.Id, user = u })) + .ToList(); } - [Returns] - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetFolderById)}")] - public Object GetFolderById(Int64 id) + [HttpGet(nameof(GetFolderById))] + public ActionResult GetFolderById(Int64 id, Token authToken) { - var user = GetSession()?.User; + var user = Db.GetSession(authToken)?.User; if (user == null) - return _Unauthorized; + return Unauthorized(); var folder = Db.GetFolderById(id); if (folder is null || !user.HasAccessTo(folder)) - return _Unauthorized; + return Unauthorized(); return folder; } - [Returns] // assuming swagger knows about arrays but not lists (JSON) - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetAllInstallations)}/")] - public Object GetAllInstallations() + [HttpGet(nameof(GetAllInstallations))] + public ActionResult> GetAllInstallations(Token authToken) { - var user = GetSession()?.User; + var user = Db.GetSession(authToken)?.User; + + if (user is null) + return Unauthorized(); - return user is null - ? _Unauthorized - : user.AccessibleInstallations(); + return user.AccessibleInstallations().ToList(); } - [Returns] // assuming swagger knows about arrays but not lists (JSON) - [Returns(Unauthorized)] - [HttpGet($"{nameof(GetAllFolders)}/")] - public Object GetAllFolders() + + [HttpGet(nameof(GetAllFolders))] + public ActionResult> GetAllFolders(Token authToken) { - var user = GetSession()?.User; + var user = Db.GetSession(authToken)?.User; + + if (user is null) + return Unauthorized(); - return user is null - ? _Unauthorized - : user.AccessibleFolders(); + return new(user.AccessibleFolders()); } - // [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(Unauthorized)] - [HttpGet($"{nameof(GetAllFoldersAndInstallations)}/")] - public Object GetAllFoldersAndInstallations() + + [HttpGet(nameof(GetAllFoldersAndInstallations))] + public ActionResult> GetAllFoldersAndInstallations(Token authToken) { - var user = GetSession()?.User; - - return user is null - ? _Unauthorized - : user.AccessibleFoldersAndInstallations(); - } + var user = Db.GetSession(authToken)?.User; + if (user is null) + return Unauthorized(); + + return new (user.AccessibleFoldersAndInstallations()); + } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(CreateUser)}/")] - public Object CreateUser(User newUser) + [HttpPost(nameof(CreateUser))] + public ActionResult CreateUser(User newUser, Token authToken) { - var session = GetSession(); - - return session.Create(newUser) + return Db.GetSession(authToken).Create(newUser) ? newUser - : _Unauthorized ; + : Unauthorized() ; } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(CreateInstallation)}/")] - public Object CreateInstallation(Installation installation) + [HttpPost(nameof(CreateInstallation))] + public async Task> CreateInstallation(Installation installation, Token authToken) { - var session = GetSession(); - - return session.Create(installation) - ? installation - : _Unauthorized; - } - - [Returns(OK)] - [Returns(Unauthorized)] - [Returns(InternalServerError)] - [HttpPost($"{nameof(CreateFolder)}/")] - public Object CreateFolder(Folder folder) - { - var session = GetSession(); - - return session.Create(folder) - ? folder - : _Unauthorized; - } - - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(GrantUserAccessToFolder)}/")] - public Object GrantUserAccessToFolder([FromQuery] Int64 folderId, [FromQuery] Int64? id) - { - var session = GetSession(); - var user = id is not null ? Db.GetUserById(id) : session?.User; - - return session.GrantUserAccessTo(user, Db.GetFolderById(folderId)) - ? _Ok - : _Unauthorized; - } - - - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(GrantUserAccessToInstallation)}/")] - public Object GrantUserAccessToInstallation([FromQuery] Int64 installationId, [FromQuery] Int64? id) - { - var session = GetSession(); + if (!await Db.GetSession(authToken).Create(installation)) + return Unauthorized(); - var user = id is not null ? Db.GetUserById(id) : session?.User; - - return session.GrantUserAccessTo(user, Db.GetInstallationById(installationId)) - ? _Ok - : _Unauthorized; + return installation; } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(RevokeUserAccessToInstallation)}/")] - public Object RevokeUserAccessToInstallation([FromQuery] Int64 installationId, [FromQuery] Int64? id) + [HttpPost(nameof(CreateFolder))] + public ActionResult CreateFolder(Folder folder, Token authToken) { - var session = GetSession(); - var user = id is not null ? Db.GetUserById(id) : session?.User; + var session = Db.GetSession(authToken); + if (!session.Create(folder)) + return Unauthorized(); - return session.RevokeAccessTo(user, Db.GetInstallationById(installationId)) - ? _Ok - : _Unauthorized; + return folder; } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPost($"{nameof(RevokeUserAccessToFolder)}/")] - public Object RevokeUserAccessToFolder([FromQuery] Int64 folderId, [FromQuery] Int64? id) + [HttpPost(nameof(GrantUserAccessToFolder))] + public ActionResult GrantUserAccessToFolder(FolderAccess folderAccess, Token authToken) { - var session = GetSession(); - var user = id is not null ? Db.GetUserById(id) : session?.User; + var session = Db.GetSession(authToken); + // TODO: automatic BadRequest when properties are null during deserialization + var folder = Db.GetFolderById(folderAccess.FolderId); + var user = Db.GetUserById(folderAccess.UserId); + + return session.GrantUserAccessTo(user, folder) + ? Ok() + : Unauthorized(); + } + + + [HttpPost(nameof(RevokeUserAccessToFolder))] + public ActionResult RevokeUserAccessToFolder(FolderAccess folderAccess, Token authToken) + { + var session = Db.GetSession(authToken); + + // TODO: automatic BadRequest when properties are null during deserialization + var folder = Db.GetFolderById(folderAccess.FolderId); + var user = Db.GetUserById(folderAccess.UserId); + + return session.RevokeUserAccessTo(user, folder) + ? Ok() + : Unauthorized(); + } + + + [HttpPost(nameof(GrantUserAccessToInstallation))] + public ActionResult GrantUserAccessToInstallation(InstallationAccess installationAccess, Token authToken) + { + var session = Db.GetSession(authToken); + + // TODO: automatic BadRequest when properties are null during deserialization + var installation = Db.GetFolderById(installationAccess.InstallationId); + var user = Db.GetUserById(installationAccess.UserId); - return session.RevokeAccessTo(user, Db.GetFolderById(folderId)) - ? _Ok - : _Unauthorized; + return session.GrantUserAccessTo(user, installation) + ? Ok() + : Unauthorized(); } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPut($"{nameof(UpdateUser)}/")] - public Object UpdateUser(User updatedUser) + [HttpPost(nameof(RevokeUserAccessToInstallation))] + public ActionResult RevokeUserAccessToInstallation(InstallationAccess installationAccess, Token authToken) { - var session = GetSession(); + var session = Db.GetSession(authToken); + + // TODO: automatic BadRequest when properties are null during deserialization + var installation = Db.GetFolderById(installationAccess.InstallationId); + var user = Db.GetUserById(installationAccess.UserId); + + return session.RevokeUserAccessTo(user, installation) + ? Ok() + : Unauthorized(); + } + + + + [HttpPut(nameof(UpdateUser))] + public ActionResult UpdateUser(User updatedUser, Token authToken) + { + var session = Db.GetSession(authToken); - if (!session.Update(updatedUser)) return _Unauthorized; - updatedUser.Password = ""; + if (!session.Update(updatedUser)) + return Unauthorized(); + + updatedUser.Password = ""; // TODO: generic sanitize return values + return updatedUser; } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPut($"{nameof(UpdateInstallation)}/")] - public Object UpdateInstallation(Installation installation) + [HttpPut(nameof(UpdateInstallation))] + public ActionResult UpdateInstallation(Installation installation, Token authToken) { - var session = GetSession(); + var session = Db.GetSession(authToken); + + if (!session.Update(installation)) + return Unauthorized(); - return session.Update(installation) - ? installation - : _Unauthorized; + return installation; } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpPut($"{nameof(UpdateFolder)}/")] - public Object UpdateFolder(Folder folder) + [HttpPut(nameof(UpdateFolder))] + public ActionResult UpdateFolder(Folder folder, Token authToken) { - var session = GetSession(); + var session = Db.GetSession(authToken); + + if (!session.Update(folder)) + return Unauthorized(); - return session.Update(folder) - ? folder - : _Unauthorized; + return folder; } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpDelete($"{nameof(DeleteUser)}/")] - public Object DeleteUser(Int64 userId) + [HttpDelete(nameof(DeleteUser))] + public ActionResult DeleteUser(Int64 userId, Token authToken) { - var session = GetSession(); + var session = Db.GetSession(authToken); var user = Db.GetUserById(userId); return session.Delete(user) - ? _Ok - : _Unauthorized; + ? Ok() + : Unauthorized(); } - [Returns(OK)] - [Returns(Unauthorized)] - [HttpDelete($"{nameof(DeleteInstallation)}/")] - public Object DeleteInstallation(Int64 installationId) + [HttpDelete(nameof(DeleteInstallation))] + public ActionResult DeleteInstallation(Int64 installationId, Token authToken) { - var session = GetSession(); + var session = Db.GetSession(authToken); var installation = Db.GetInstallationById(installationId); return session.Delete(installation) - ? _Ok - : _Unauthorized; + ? Ok() + : Unauthorized(); } - - [ProducesResponseType(200)] - [ProducesResponseType(401)] - [HttpDelete($"{nameof(DeleteFolder)}/")] - public Object DeleteFolder(Int64 folderId) - { - var session = GetSession(); - var folder = Db.GetFolderById(folderId); + [HttpDelete(nameof(DeleteFolder))] + public ActionResult DeleteFolder(Int64 folderId, Token authToken) + { + var session = Db.GetSession(authToken); + var folder = Db.GetFolderById(folderId); return session.Delete(folder) - ? _Ok - : _Unauthorized; + ? Ok() + : Unauthorized(); } - private static Session? GetSession() - { - var ctxAccessor = new HttpContextAccessor(); - return ctxAccessor.HttpContext?.Items["Session"] as Session; - } + } diff --git a/csharp/App/Backend/Controllers/ReturnsAttribute.cs b/csharp/App/Backend/Controllers/ReturnsAttribute.cs deleted file mode 100644 index 2ef924055..000000000 --- a/csharp/App/Backend/Controllers/ReturnsAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Net; -using Microsoft.AspNetCore.Mvc; - -namespace InnovEnergy.App.Backend.Controllers; - -public class ReturnsAttribute : ProducesResponseTypeAttribute -{ - public ReturnsAttribute(HttpStatusCode statusCode) : base((Int32)statusCode) - { - } -} - -public class ReturnsAttribute : ProducesResponseTypeAttribute -{ - public ReturnsAttribute(HttpStatusCode statusCode) : base(typeof(T), (Int32)statusCode) - { - } - - public ReturnsAttribute() : base(typeof(T), (Int32)HttpStatusCode.OK) - { - } -} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Credentials.cs b/csharp/App/Backend/DataTypes/Credentials.cs deleted file mode 100644 index d17c3f063..000000000 --- a/csharp/App/Backend/DataTypes/Credentials.cs +++ /dev/null @@ -1,6 +0,0 @@ -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/Methods/Credentials.cs b/csharp/App/Backend/DataTypes/Methods/Credentials.cs deleted file mode 100644 index 2e29149b1..000000000 --- a/csharp/App/Backend/DataTypes/Methods/Credentials.cs +++ /dev/null @@ -1,27 +0,0 @@ -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) - { - var (username, password) = credentials; - - if (username.IsNullOrEmpty() || password.IsNullOrEmpty()) - return null; - - var user = Db.GetUserByEmail(username); - - if (user is null || !user.VerifyPassword(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 index 404410997..68c12c68b 100644 --- a/csharp/App/Backend/DataTypes/Methods/Folder.cs +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -1,4 +1,3 @@ -using System.Collections; using InnovEnergy.App.Backend.Database; using InnovEnergy.Lib.Utils; @@ -9,20 +8,27 @@ public static class FolderMethods public static IEnumerable UsersWithAccess(this Folder folder) { - return UsersWithDirectAccess(folder).Concat(UsersWithInheritedAccess(folder)); + var direct = folder.UsersWithDirectAccess(); + var inherited = folder.UsersWithInheritedAccess(); + + return direct.Concat(inherited); } public static IEnumerable UsersWithDirectAccess(this Folder folder) { - return Db.FolderAccess - .Where(access => access.FolderId == folder.Id) - .Select(access => Db.GetUserById(access.UserId)) - .NotNull(); + return Db + .FolderAccess + .Where(a => a.FolderId == folder.Id) + .Select(a => Db.GetUserById(a.UserId)) + .NotNull(); } public static IEnumerable UsersWithInheritedAccess(this Folder folder) { - return folder.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull(); + return folder + .Ancestors() + .SelectMany(f => f.UsersWithDirectAccess()) + .NotNull(); } public static IEnumerable ChildFolders(this Folder parent) @@ -74,7 +80,7 @@ public static class FolderMethods public static Boolean IsRelativeRoot(this Folder folder) { - return folder.ParentId < 0; + return folder.ParentId < 0; // TODO } public static Boolean WasMoved(this Folder folder) diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs index 608827041..134db9c11 100644 --- a/csharp/App/Backend/DataTypes/Methods/Installation.cs +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -1,6 +1,5 @@ -using CliWrap; -using CliWrap.Buffered; using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.S3; using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.Backend.DataTypes.Methods; @@ -8,96 +7,59 @@ namespace InnovEnergy.App.Backend.DataTypes.Methods; public static class InstallationMethods { - public static async Task RenewS3BucketUrl(this Installation installation) + private const String BucketNameSalt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; + + public static String BucketName(this Installation installation) { - await RenewS3BucketUrl(installation, TimeSpan.FromDays(1)); + return $"s3://{installation.Id}-{BucketNameSalt}"; } - public static async Task RenewS3BucketUrl(this Installation installation, TimeSpan validity) + public static async Task RenewS3BucketUrl(this Installation installation) { - const String secret = "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU"; - const String apiKey = "EXO44d2979c8e570eae81ead564"; - const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; - var cmd = Cli - .Wrap("python3") - .WithArguments(new[] - { - "Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}", validity.TotalSeconds.ToString(), "--access_key", - apiKey, "--secret_key", secret - }); - var x = await cmd.ExecuteBufferedAsync(); - installation.S3Url = x.StandardOutput.Replace("\n", "").Replace(" ", ""); + return await RenewS3BucketUrl(installation, TimeSpan.FromDays(1)); + } - Db.Update(installation); + public static async Task RenewS3BucketUrl(this Installation installation, TimeSpan validity) + { + installation.S3Url = await S3Access.ReadOnly.SignUrl(installation.BucketName(), validity); + return Db.Update(installation); } - - public static async Task CreateBucket(this Installation installation) + public static Task CreateBucket(this Installation installation) { - const String secret = "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"; - - const String apiKey = "EXO87ca85e29dd412f1238f1cf0"; - const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; - - var cmd = Cli - .Wrap("python3") - .WithArguments(new[] - { - "Resources/s3cmd.py", "mb", $"s3://{installation.Id}-{salt}", "--access_key", - apiKey, "--secret_key", secret - }); - var x = await cmd.ExecuteBufferedAsync(); - - //Updating the url in the db as not wait until the next bi-daily update - var cmd2 = Cli - .Wrap("python3") - .WithArguments(new[] - { - "Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}", - TimeSpan.FromDays(1).TotalSeconds.ToString(), "--access_key", - apiKey, "--secret_key", secret - }); - - var y = await cmd2.ExecuteBufferedAsync(); - installation.S3Url = y.StandardOutput.Replace("\n", "").Replace(" ", ""); - - Db.Update(installation); - - return x.ExitCode == 0; + return S3Access + .ReadWrite + .CreateBucket(installation.BucketName()); } - public static async Task DeleteBucket(this Installation installation) + public static Task DeleteBucket(this Installation installation) { - const String secret = "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"; - const String apiKey = "EXO87ca85e29dd412f1238f1cf0"; - const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; - var cmd = Cli - .Wrap("python3") - .WithArguments(new[] - { - "Resources/s3cmd.py", "rb", $"s3://{installation.Id}-{salt}", "--access_key", - apiKey, "--secret_key", secret - }); - var x = await cmd.ExecuteBufferedAsync(); - return x.ExitCode == 0; + return S3Access + .ReadWrite + .DeleteBucket(installation.BucketName()); } public static IEnumerable UsersWithAccess(this Installation installation) { - return UsersWithDirectAccess(installation).Concat(UsersWithInheritedAccess(installation)); + return installation.UsersWithDirectAccess() + .Concat(installation.UsersWithInheritedAccess()); } public static IEnumerable UsersWithDirectAccess(this Installation installation) { - return Db.InstallationAccess - .Where(access => access.InstallationId == installation.Id) - .Select(access => Db.GetUserById(access.UserId)) - .NotNull(); + return Db + .InstallationAccess + .Where(a => a.InstallationId == installation.Id) + .Select(a => Db.GetUserById(a.UserId)) + .NotNull(); } public static IEnumerable UsersWithInheritedAccess(this Installation installation) { - return installation.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull(); + return installation + .Ancestors() + .SelectMany(f => f.UsersWithDirectAccess()) + .NotNull(); } public static IEnumerable Ancestors(this Installation installation) diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 5b8dfae0a..a1ecf900c 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -14,7 +14,7 @@ public static class SessionMethods && user.HasWriteAccess && user.HasAccessTo(folder.Parent()) && Db.Create(folder) - && Db.Create(new FolderAccess() { UserId = user.Id, FolderId = folder.Id }); + && Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id }); } public static Boolean Update(this Session? session, Folder? folder) @@ -41,19 +41,19 @@ public static class SessionMethods } - public static Boolean Create(this Session? session, Installation? installation) + public static async Task Create(this Session? session, Installation? installation) { var user = session?.User; - - //Note: keep generation of access _after_ generation of object to prevent "zombie" access-rights. - + return user is not null && installation is not null && user.HasWriteAccess - && user.HasAccessTo(installation.Parent()) + && user.HasAccessTo(installation.Parent()) && Db.Create(installation) - && installation.CreateBucket().Result - && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }); + && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }) + && await installation.CreateBucket() + && await installation.RenewS3BucketUrl(); // generation of access _after_ generation of + // bucket to prevent "zombie" access-rights. } public static Boolean Update(this Session? session, Installation? installation) @@ -77,6 +77,7 @@ public static class SessionMethods && installation is not null && user.HasWriteAccess && user.HasAccessTo(installation) + // && installation.DeleteBucket().Result // TODO: await? && Db.Delete(installation); } @@ -97,13 +98,11 @@ public static class SessionMethods var sessionUser = session?.User; if (editedUser == null || sessionUser == null) return false; - - //Password change is only allowed for oneself - if ( editedUser.Id != sessionUser.Id) editedUser.Password = sessionUser.Password; - else - { - editedUser.Password = sessionUser.SaltAndHashPassword(editedUser.Password); - } + // TODO: make specific method for changing user account settings like pwd + // Password change is only allowed for oneself + editedUser.Password = editedUser.Id != sessionUser.Id + ? sessionUser.Password + : sessionUser.SaltAndHashPassword(editedUser.Password); return sessionUser.HasWriteAccess && sessionUser.HasAccessTo(editedUser) @@ -149,38 +148,36 @@ public static class SessionMethods && Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id }); } - public static Boolean RevokeAccessTo(this Session? session, User? user, Installation? installation) + public static Boolean RevokeUserAccessTo(this Session? session, User? user, Installation? installation) { var sessionUser = session?.User; return sessionUser is not null - && user is not null - && installation is not null - && user.IsDescendantOf(sessionUser) - && sessionUser.HasAccessTo(installation) - && user.HasAccessTo(installation) - && Db.InstallationAccess.Delete(access => - access.UserId == user.Id && access.InstallationId == installation.Id) > 0; + && user is not null + && installation is not null + && user.IsDescendantOf(sessionUser) + && sessionUser.HasAccessTo(installation) + && user.HasAccessTo(installation) + && Db.InstallationAccess.Delete(a => a.UserId == user.Id && a.InstallationId == installation.Id) > 0; } - public static Boolean RevokeAccessTo(this Session? session, User? user, Folder? folder) + public static Boolean RevokeUserAccessTo(this Session? session, User? user, Folder? folder) { var sessionUser = session?.User; return sessionUser is not null - && user is not null - && folder is not null - && user.IsDescendantOf(sessionUser) - && sessionUser.HasAccessTo(folder) - && user.HasAccessTo(folder) - && Db.FolderAccess.Delete(access => - access.UserId == user.Id && access.FolderId == folder.Id) > 0; + && user is not null + && folder is not null + && user.IsDescendantOf(sessionUser) + && sessionUser.HasAccessTo(folder) + && user.HasAccessTo(folder) + && Db.FolderAccess.Delete(a => a.UserId == user.Id && a.FolderId == folder.Id) > 0; } public static Boolean Logout(this Session? session) { return session is not null - && Db.Sessions.Delete(s => s.Token == session.Token) > 0; + && 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 index f0d2c347f..410b4680b 100644 --- a/csharp/App/Backend/DataTypes/Methods/User.cs +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -32,7 +32,9 @@ public static class UserMethods // Distinct because the user might have direct access // to a child folder of a folder he has already access to - // ---TODO shouldn't we prevent doubling permissions? -K" + // TODO shouldn't we prevent doubling permissions? -K" + // TODO yes we should -ig (still TODO) + // however we should leave the distinct, defensive programming... } public static IEnumerable AccessibleFoldersAndInstallations(this User user) @@ -81,7 +83,6 @@ public static class UserMethods public static Boolean IsDescendantOf(this User user, User ancestor) { - // if (user.Id == ancestor.Id) return true; return user .Ancestors() .Any(u => u.Id == ancestor.Id); diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index f7eaafd7f..80cd15454 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -6,7 +6,6 @@ using InnovEnergy.Lib.Utils; using SQLite; - namespace InnovEnergy.App.Backend.Database; @@ -38,9 +37,6 @@ public static partial class Db Connection.CreateTable(); }); - - - Observable.Interval(TimeSpan.FromDays(0.5)) .StartWith(0) // Do it right away (on startup) .SelectMany(Cleanup) @@ -85,7 +81,8 @@ public static partial class Db private static Task UpdateS3Urls() { - var renewTasks = Installations.Select(i => i.RenewS3BucketUrl()).ToArray(); - return Task.WhenAll(renewTasks); + return Installations + .Select(i => i.RenewS3BucketUrl()) + .WhenAll(); } } \ No newline at end of file diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index e7500ff1a..385c6250b 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -1,4 +1,3 @@ -using InnovEnergy.App.Backend.Database; using Microsoft.OpenApi.Models; namespace InnovEnergy.App.Backend; @@ -7,57 +6,50 @@ public static class Program { public static void Main(String[] args) { - Db.CreateFakeRelations(); + //Db.CreateFakeRelations(); var builder = WebApplication.CreateBuilder(args); - - builder.Services.AddControllers(); // TODO: remove magic, specify controllers explicitly - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - - builder.Services.AddHttpContextAccessor(); - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.WithOrigins("*").AllowAnyHeader().AllowAnyMethod())); + + //builder.Services.AddHttpContextAccessor(); + //builder.Services.AddEndpointsApiExplorer(); + //builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod())); + + builder.Services.AddControllers(); builder.Services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "InnovEnergy Backend API", Version = "v1" }); + c.SwaggerDoc("v1", OpenApiInfo); c.UseAllOfToExtendReferenceSchemas(); - c.OperationFilter(); //Todo testing throw me out + c.SupportNonNullableReferenceTypes(); }); - var app = builder.Build(); - // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(cfg => cfg.EnableFilter()); + app.UseSwaggerUI(); } - - app.UseCors(); + + //app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ; app.UseHttpsRedirection(); - app.UseAuthorization(); - app.Use(SetSessionUser); + //app.UseAuthorization(); app.MapControllers(); app.Run(); } - - - private static async Task SetSessionUser(HttpContext ctx, RequestDelegate next) + private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo { - var headers = ctx.Request.Headers; - var hasToken = headers.TryGetValue("auth", out var token) ; + Title = "InnovEnergy Backend API", + Version = "v1" + }; - if (hasToken) - { - var session = Db.GetSession(token); +} - if (session is not null) - ctx.Items["Session"] = session; - } - await next(ctx); - } -} \ No newline at end of file +// var x = new CorsPolicy +// { +// Headers = { "*" }, +// Origins = { "*" }, +// Methods = { "*" } +// }; diff --git a/csharp/App/Backend/S3/S3Access.cs b/csharp/App/Backend/S3/S3Access.cs new file mode 100644 index 000000000..0fe957ad5 --- /dev/null +++ b/csharp/App/Backend/S3/S3Access.cs @@ -0,0 +1,21 @@ +namespace InnovEnergy.App.Backend.S3; + +public static class S3Access +{ + // TODO: put these into Json files in /Resources and read them from + // there so they can be changed without recompiling + // they should be read from disk on each use, + // so the backend does not need to be restarted on change + + public static S3Cmd ReadOnly { get; } = new S3Cmd + ( + key : "EXO44d2979c8e570eae81ead564", + secret: "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU" + ); + + public static S3Cmd ReadWrite { get; } = new S3Cmd + ( + key : "EXO87ca85e29dd412f1238f1cf0", + secret: "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU" + ); +} \ No newline at end of file diff --git a/csharp/App/Backend/S3/S3Cmd.cs b/csharp/App/Backend/S3/S3Cmd.cs new file mode 100644 index 000000000..b8bd8f890 --- /dev/null +++ b/csharp/App/Backend/S3/S3Cmd.cs @@ -0,0 +1,62 @@ +using CliWrap; +using CliWrap.Buffered; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.S3; + +public class S3Cmd +{ + private static readonly Command Python = Cli.Wrap("python3"); + + private const String S3CmdPath = "Resources/s3cmd.py"; + private const String S3Prefix = "s3://"; + + private String[] DefaultArgs { get; } + + // ReSharper disable StringLiteralTypo + // ReSharper enable StringLiteralTypo + + public S3Cmd(String key, String secret) + { + DefaultArgs = new[] + { + S3CmdPath, + "--access_key", key, + "--secret_key", secret, + }; + } + + public async Task SignUrl(String bucketName, TimeSpan validity) + { + var result = await Run(bucketName, "signurl", $"+{validity.TotalSeconds}"); + + return result + .StandardOutput + .Replace("\n", "") + .Replace(" ", ""); + } + + public async Task CreateBucket(String bucketName) + { + var result = await Run(bucketName, "mb"); + return result.ExitCode == 0; + } + + public async Task DeleteBucket(String bucketName) + { + var result = await Run(bucketName, "rb"); + return result.ExitCode == 0; + } + + private Task Run(String bucketName, String operation, params String[] optionalArgs) + { + var args = DefaultArgs + .Append(operation) + .Append(bucketName.EnsureStartsWith(S3Prefix)) + .Concat(optionalArgs); + + return Python + .WithArguments(args) + .ExecuteBufferedAsync(); + } +} \ No newline at end of file diff --git a/csharp/App/Backend/db.sqlite b/csharp/App/Backend/db.sqlite index ea45e7dbd..8cddf6109 100644 Binary files a/csharp/App/Backend/db.sqlite and b/csharp/App/Backend/db.sqlite differ diff --git a/csharp/InnovEnergy.sln.DotSettings b/csharp/InnovEnergy.sln.DotSettings index 0584ce254..116325457 100644 --- a/csharp/InnovEnergy.sln.DotSettings +++ b/csharp/InnovEnergy.sln.DotSettings @@ -33,6 +33,7 @@ True True True + True True True True diff --git a/csharp/lib/Devices/Adam6060/Doc/ADAM-6000_User_Manaul_Ed_9-51-53.pdf b/csharp/Lib/Devices/Adam6060/Doc/ADAM-6000_User_Manaul_Ed_9-51-53.pdf similarity index 100% rename from csharp/lib/Devices/Adam6060/Doc/ADAM-6000_User_Manaul_Ed_9-51-53.pdf rename to csharp/Lib/Devices/Adam6060/Doc/ADAM-6000_User_Manaul_Ed_9-51-53.pdf diff --git a/csharp/lib/Devices/Adam6060/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf b/csharp/Lib/Devices/Adam6060/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf similarity index 100% rename from csharp/lib/Devices/Adam6060/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf rename to csharp/Lib/Devices/Adam6060/Doc/ADAM-6360D-A1_DS(080321)20210804101056.pdf diff --git a/csharp/lib/Devices/Battery48TL/Doc/2023_02_20_48TL200_AF06_Firmware Release Note.pdf b/csharp/Lib/Devices/Battery48TL/Doc/2023_02_20_48TL200_AF06_Firmware Release Note.pdf similarity index 100% rename from csharp/lib/Devices/Battery48TL/Doc/2023_02_20_48TL200_AF06_Firmware Release Note.pdf rename to csharp/Lib/Devices/Battery48TL/Doc/2023_02_20_48TL200_AF06_Firmware Release Note.pdf diff --git a/csharp/lib/Devices/Battery48TL/Doc/EOC Procedure for 48Tl200.pdf b/csharp/Lib/Devices/Battery48TL/Doc/EOC Procedure for 48Tl200.pdf similarity index 100% rename from csharp/lib/Devices/Battery48TL/Doc/EOC Procedure for 48Tl200.pdf rename to csharp/Lib/Devices/Battery48TL/Doc/EOC Procedure for 48Tl200.pdf diff --git a/csharp/lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf b/csharp/Lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf similarity index 100% rename from csharp/lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf rename to csharp/Lib/Devices/Battery48TL/Doc/TI_48TL200 ModBus protocol_rev.INNOVENERGY.pdf diff --git a/csharp/lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf b/csharp/Lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf similarity index 100% rename from csharp/lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf rename to csharp/Lib/Devices/Battery48TL/Doc/TI_48TLxxx ModBus protocol_rev.7.1-1GoodOne.pdf diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/Doc/A67-0141-00.BKde-001-09.pdf b/csharp/Lib/Devices/Trumpf/TruConvertAc/Doc/A67-0141-00.BKde-001-09.pdf similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertAc/Doc/A67-0141-00.BKde-001-09.pdf rename to csharp/Lib/Devices/Trumpf/TruConvertAc/Doc/A67-0141-00.BKde-001-09.pdf diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/Doc/TRUMPF_Betriebsanleitung_TruConvert_DC_1000_Serie.pdf b/csharp/Lib/Devices/Trumpf/TruConvertDc/Doc/TRUMPF_Betriebsanleitung_TruConvert_DC_1000_Serie.pdf similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/Doc/TRUMPF_Betriebsanleitung_TruConvert_DC_1000_Serie.pdf rename to csharp/Lib/Devices/Trumpf/TruConvertDc/Doc/TRUMPF_Betriebsanleitung_TruConvert_DC_1000_Serie.pdf