diff --git a/csharp/App/Backend/Controllers/Controller.cs b/csharp/App/Backend/Controllers/Controller.cs index e3c285dcb..46d6d67ad 100644 --- a/csharp/App/Backend/Controllers/Controller.cs +++ b/csharp/App/Backend/Controllers/Controller.cs @@ -81,7 +81,57 @@ public class Controller return installation; } + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetUsersWithAccessToInstallation)}")] + public Object GetUsersWithAccessToInstallation(Int64 id) + { + var user = GetSession()?.User; + if (user == null) + return _Unauthorized; + + var installation = Db.GetInstallationById(id); + + if (installation is null || !user.HasAccessTo(installation)) + 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 usersWithDirectAccess = installation.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { installationId = installation.Id, user = u }) + .OfType(); + + return usersWithInheritedAccess.Concat(usersWithDirectAccess); + } + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetUsersWithAccessToFolder)}")] + public Object GetUsersWithAccessToFolder(Int64 id) + { + var user = GetSession()?.User; + if (user == null) + return _Unauthorized; + + var folder = Db.GetFolderById(id); + + if (folder is null || !user.HasAccessTo(folder)) + return _Unauthorized; + + return folder + .Ancestors() + .Append(folder) + .SelectMany(f => f.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { folderId = f.Id, user = u })); + } [Returns] [Returns(Unauthorized)] diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index ebc9244c7..f149135a3 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -15,5 +15,5 @@ public class Installation : TreeNode public String S3Bucket { get; set; } = ""; public String S3Url { 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 index 1a8e24703..2e29149b1 100644 --- a/csharp/App/Backend/DataTypes/Methods/Credentials.cs +++ b/csharp/App/Backend/DataTypes/Methods/Credentials.cs @@ -8,12 +8,14 @@ public static class CredentialsMethods { public static Session? Login(this Credentials credentials) { - if (credentials.Username.IsNullOrEmpty() || credentials.Password.IsNullOrEmpty()) + var (username, password) = credentials; + + if (username.IsNullOrEmpty() || password.IsNullOrEmpty()) return null; - var user = Db.GetUserByEmail(credentials.Username); + var user = Db.GetUserByEmail(username); - if (user is null || !user.VerifyPassword(credentials.Password)) + if (user is null || !user.VerifyPassword(password)) return null; var session = new Session(user); diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs index 321aca9eb..b6543e53e 100644 --- a/csharp/App/Backend/DataTypes/Methods/Folder.cs +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -1,3 +1,4 @@ +using System.Collections; using InnovEnergy.App.Backend.Database; using InnovEnergy.Lib.Utils; @@ -5,6 +6,25 @@ namespace InnovEnergy.App.Backend.DataTypes.Methods; public static class FolderMethods { + + public static IEnumerable UsersWithAccess(this Folder folder) + { + return UsersWithDirectAccess(folder).Concat(UsersWithInheritedAccess(folder)); + } + + public static IEnumerable UsersWithDirectAccess(this Folder folder) + { + return Db.FolderAccess + .Where(access => access.FolderId == folder.Id) + .Select(access => Db.GetUserById(access.UserId)) + .NotNull(); + } + + public static IEnumerable UsersWithInheritedAccess(this Folder folder) + { + return folder.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull(); + } + public static IEnumerable ChildFolders(this Folder parent) { return Db @@ -21,7 +41,9 @@ public static class FolderMethods public static IEnumerable DescendantFolders(this Folder parent) { - return parent.Traverse(ChildFolders); + return parent + .TraverseDepthFirstPreOrder(ChildFolders) + .Skip(1); // skip self } public static Boolean IsDescendantOf(this Folder folder, Folder ancestor) diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs index 3ac08bdb2..e27b3be67 100644 --- a/csharp/App/Backend/DataTypes/Methods/Installation.cs +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -1,6 +1,7 @@ using CliWrap; using CliWrap.Buffered; using InnovEnergy.App.Backend.Database; +using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.Backend.DataTypes.Methods; @@ -11,10 +12,10 @@ public static class InstallationMethods { await RenewS3BucketUrl(installation, TimeSpan.FromDays(1)); } - + public static async Task RenewS3BucketUrl(this Installation installation, TimeSpan validity) { - //secret 55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU + const String secret = "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU"; const String apiKey = "EXO44d2979c8e570eae81ead564"; const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; var cmd = Cli @@ -22,16 +23,70 @@ public static class InstallationMethods .WithArguments(new[] { "Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}", validity.TotalSeconds.ToString(), "--access_key", - apiKey + apiKey, "--secret_key", secret }); var x = await cmd.ExecuteBufferedAsync(); installation.S3Url = x.StandardOutput.Replace("\n", "").Replace(" ", ""); - Console.WriteLine(installation.S3Url); - - Db.Update(installation); + Db.Update(installation); } + + public static async Task CreateBucket(this Installation installation) + { + //NOTE this key has all the rights, please be sure you know what you're doing + + const String secret = "z8brNDUAbpktvyWZN1jMIrsQhavDgK2t4cb8GLvsxYg"; + + const String apiKey = "EXO277645911ee6bde3875e99ae"; + 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(); + return x.ExitCode == 0; + } + + public static async Task DeleteBucket(this Installation installation) + { + //NOTE this key has all the rights, please be sure you know what you're doing + const String secret = "z8brNDUAbpktvyWZN1jMIrsQhavDgK2t4cb8GLvsxYg"; + const String apiKey = "EXO277645911ee6bde3875e99ae"; + 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 + }); + var x = await cmd.ExecuteBufferedAsync(); + return x.ExitCode == 0; + } + + + public static IEnumerable UsersWithAccess(this Installation installation) + { + return UsersWithDirectAccess(installation).Concat(UsersWithInheritedAccess(installation)); + } + + public static IEnumerable UsersWithDirectAccess(this Installation installation) + { + return Db.InstallationAccess + .Where(access => access.InstallationId == installation.Id) + .Select(access => Db.GetUserById(access.UserId)) + .NotNull(); + } + + public static IEnumerable UsersWithInheritedAccess(this Installation installation) + { + return installation.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull(); + } + public static IEnumerable Ancestors(this Installation installation) { var parentFolder = Parent(installation); diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 844b1cca2..6cc3616c6 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Relations; @@ -48,7 +49,8 @@ public static class SessionMethods && installation is not null && user.HasWriteAccess && user.HasAccessTo(installation.Parent()) - && Db.Create(installation); + && Db.Create(installation) + && InstallationMethods.CreateBucket(installation).Result; } public static Boolean Update(this Session? session, Installation? installation) @@ -90,13 +92,20 @@ public static class SessionMethods public static Boolean Update(this Session? session, User? editedUser) { var sessionUser = session?.User; + if (editedUser == null || sessionUser == null) return false; - 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); + + //Password change is only allowed for oneself + if ( editedUser.Id != sessionUser.Id) editedUser.Password = sessionUser.Password; + else + { + editedUser.Password = sessionUser.SaltAndHashPassword(editedUser.Password); + } + + return sessionUser.HasWriteAccess + && sessionUser.HasAccessTo(editedUser) + && (editedUser.IsRelativeRoot() || sessionUser.HasAccessTo(editedUser.Parent()) || editedUser.Id == sessionUser.Id) // TODO: triple check this + && Db.Update(editedUser); } public static Boolean Delete(this Session? session, User? userToDelete) diff --git a/csharp/App/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs index b7498ccfe..97eb4d10a 100644 --- a/csharp/App/Backend/DataTypes/Methods/User.cs +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -73,12 +73,14 @@ public static class UserMethods public static IEnumerable DescendantUsers(this User parent) { - return parent.Traverse(ChildUsers); + return parent + .TraverseDepthFirstPreOrder(ChildUsers) + .Skip(1); // skip self } public static Boolean IsDescendantOf(this User user, User ancestor) { - if (user.Id == ancestor.Id) return true; + // if (user.Id == ancestor.Id) return true; return user .Ancestors() .Any(u => u.Id == ancestor.Id); diff --git a/csharp/App/Backend/Database/Update.cs b/csharp/App/Backend/Database/Update.cs index 98c5a64fe..72aef3015 100644 --- a/csharp/App/Backend/Database/Update.cs +++ b/csharp/App/Backend/Database/Update.cs @@ -40,16 +40,12 @@ public static partial class Db public static Boolean Update(User user) { var originalUser = GetUserById(user.Id); - - //Todo change password backend - user.Password = originalUser.Password; - + 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; + && user.Id == originalUser.Id // these columns must not be modified! + && user.ParentId == originalUser.ParentId + && user.Email == originalUser.Email + && Connection.InsertOrReplace(user) > 0; } public static Boolean Update(this Session session) diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index d32326b7a..e7500ff1a 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -1,4 +1,3 @@ -using System.Reactive.Linq; using InnovEnergy.App.Backend.Database; using Microsoft.OpenApi.Models; @@ -8,7 +7,6 @@ public static class Program { public static void Main(String[] args) { - Db.CreateFakeRelations(); var builder = WebApplication.CreateBuilder(args); diff --git a/csharp/App/Backend/db.sqlite b/csharp/App/Backend/db.sqlite index ef955850d..ea45e7dbd 100644 Binary files a/csharp/App/Backend/db.sqlite and b/csharp/App/Backend/db.sqlite differ diff --git a/csharp/Lib/Utils/TreeTraversal.cs b/csharp/Lib/Utils/TreeTraversal.cs new file mode 100644 index 000000000..4fef44baf --- /dev/null +++ b/csharp/Lib/Utils/TreeTraversal.cs @@ -0,0 +1,147 @@ +namespace InnovEnergy.Lib.Utils; + +public static class TreeTraversal +{ + // public static IEnumerable TraverseDepthFirstPreOrder(this T root, Func> getChildren) + // { + // var stack = new Stack>(); + // + // var iterator = root.AsSingleEnumerator(); + // + // while (true) + // { + // while (iterator.MoveNext()) + // { + // yield return iterator.Current; + // + // iterator = getChildren(iterator.Current).GetEnumerator(); + // stack.Push(iterator); + // } + // + // iterator.Dispose(); + // if (stack.Count == 0) + // yield break; + // + // iterator = stack.Pop(); + // } + // + // } + + public static IEnumerable TraverseDepthFirstPreOrder(this T root, Func> getChildren) + { + return Traverse(root, + getChildren, + branchOpen: true, + branchClose: false, + leaf: true); + } + + public static IEnumerable TraverseDepthFirstPostOrder(this T root, Func> getChildren) + { + return Traverse(root, + getChildren, + branchOpen: false, + branchClose: true, + leaf: true); + } + + public static IEnumerable TraverseLeaves(this T root, Func> getChildren) + { + return Traverse(root, + getChildren, + branchOpen: false, + branchClose: true, + leaf: false); + } + + + private static IEnumerable Traverse(T root, + Func> getChildren, + Boolean branchOpen, + Boolean branchClose, + Boolean leaf) + { + // the if-checks on the constant booleans are + // almost free because of modern branch predictors + + var stack = new Stack>(); + var it = root.AsSingleEnumerator(); + it.MoveNext(); + + while (true) + { + //////// going down //////// + + while (true) + { + var cit = getChildren(it.Current).GetEnumerator(); + + if (cit.MoveNext()) // node has children, must be a branch + { + if (branchOpen) + yield return it.Current; + + stack.Push(it); + it = cit; + } + else // no children, hence a leaf + { + var node = it.Current; + + if (leaf) + yield return node; + + if (!it.MoveNext()) + break; // no more siblings: goto parent + } + } + + //////// going up //////// + + while (true) + { + it.Dispose(); + if (stack.Count == 0) yield break; // we got to the bottom of the stack, were done + + it = stack.Pop(); + + var node = it.Current; + + if (branchClose) + yield return node; // we've seen all its children: close the branch + + if (it.MoveNext()) + break; + } + } + } + + + public static IEnumerable TraverseBreadthFirst(this T node, Func> getChildren) + { + var queue = new Queue>(); + + var iterator = node.AsSingleEnumerator(); + + while(true) + { + while (iterator.MoveNext()) + { + yield return iterator.Current; + + iterator.Current + .Apply(getChildren) + .GetEnumerator() + .Apply(queue.Enqueue); + } + + iterator.Dispose(); + + if (queue.Count == 0) + yield break; + + iterator = queue.Dequeue(); + } + + } +} \ No newline at end of file diff --git a/csharp/Lib/Utils/Utils.cs b/csharp/Lib/Utils/Utils.cs index 690d6abaa..c76476a35 100644 --- a/csharp/Lib/Utils/Utils.cs +++ b/csharp/Lib/Utils/Utils.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using static System.Runtime.CompilerServices.MethodImplOptions; @@ -6,6 +7,7 @@ namespace InnovEnergy.Lib.Utils; public static class Utils { + public static Boolean IsNull([NotNullWhen(returnValue: false)] this T t) => t is null; public static IEnumerable GetEnumStrings(this T e) where T : Enum { @@ -20,7 +22,7 @@ public static class Utils [DebuggerStepThrough][MethodImpl(AggressiveInlining)] public static T ConvertTo(this IConvertible c) where T : IConvertible { - var t = typeof (T); + var t = typeof(T); var type = t.IsEnum ? Enum.GetUnderlyingType(t) @@ -50,13 +52,13 @@ public static class Utils [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)] public static R Apply(this T t, Func f) => f(t); - [DebuggerStepThrough] - [MethodImpl(AggressiveInlining | AggressiveOptimization)] + + [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)] public static R Apply(this (T1 p1, T2 p2) t, Func f) => f(t.p1, t.p2); - [DebuggerStepThrough] - [MethodImpl(AggressiveInlining | AggressiveOptimization)] - public static R Apply(this (T1 p1, T2 p2, T3 p3) t, Func f) => f(t.p1, t.p2, t.p3); + + [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)] + public static R Apply(this (T1 p1, T2 p2, T3 p3) t, Func f) => f(t.p1, t.p2, t.p3); [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)] public static R ApplyOrDefault(this T t, Func f, R @default) @@ -79,7 +81,7 @@ public static class Utils ? res : res + length; } - + public static Decimal Modulo(this Decimal dividend, Decimal divisor) { var res = dividend % divisor; @@ -89,111 +91,39 @@ public static class Utils : res + divisor; } - public static IEnumerable Traverse(this T root, Func> getChildren) - { - var stack = new Stack>(); - var it = root.AsSingleEnumerator(); - it.MoveNext(); - - while (true) - { - //////// going down //////// - - while (true) - { - var cit = getChildren(it.Current).GetEnumerator(); - - if (cit.MoveNext()) // node has children, must be a branch - { - yield return it.Current; - - stack.Push(it); - it = cit; - } - else // no children, hence a leaf - { - var node = it.Current; - - yield return node; - - if (!it.MoveNext()) - break; // no more siblings: goto parent - } - } - - //////// going up //////// - - while (true) - { - it.Dispose(); - if (stack.Count == 0) - yield break; // we got to the bottom of the stack, were done - - it = stack.Pop(); - - if (it.MoveNext()) - break; - } - } - - } public static Int32 Clamp(this Int32 value, Int32 minValue, Int32 maxValue) { var clamped = Math.Min(maxValue, value); clamped = Math.Max(minValue, clamped); - + return clamped; } - + public static Double Clamp(this Double value, Double minValue, Double maxValue) { var clamped = Math.Min(maxValue, value); clamped = Math.Max(minValue, clamped); - + return clamped; } - - + + public static Decimal Clamp(this Decimal value, Decimal minValue, Decimal maxValue) { var clamped = Math.Min(maxValue, value); clamped = Math.Max(minValue, clamped); - + return clamped; } - -#pragma warning disable 8714 - - public static async Task ApplyOrDefault(this T t, Func> f, R @default) - { - try - { - return await f(t); - } - catch - { - return @default; - } - } - - public static R ValueOrDefault(this Dictionary dict, T key, R defaultValue) + public static R ValueOrDefault(this Dictionary dict, T key, R defaultValue) where T : notnull { return dict.TryGetValue(key, out var value) - ? value - : defaultValue; + ? value + : defaultValue; } - - public static Dictionary CombineDicts(params IEnumerable>[] dicts) - { - return dicts.Flatten().ToDictionary(kv => kv.Key, kv => kv.Value); - } - -#pragma warning restore 8714 - - public static void CopyFilesRecursively(String source, String target) { CopyFilesRecursively(new DirectoryInfo(source), new DirectoryInfo(target)); @@ -210,27 +140,4 @@ public static class Utils public static String ExecutingProcessName => Process.GetCurrentProcess().ProcessName; - - public static IEnumerable> TraverseWithPath(this T root, Func> getChildren) - { - var stack = new Stack>(); - stack.Push(root.AsSingleEnumerator()); - - do - { - for (var top = stack.Peek(); top.MoveNext(); top = Push(top)) - yield return stack.Select(p => p.Current); - - var popped = stack.Pop(); - popped.Dispose(); - } - while (stack.Count > 0); - - IEnumerator Push(IEnumerator node) - { - var top = getChildren(node.Current).GetEnumerator(); - stack.Push(top); - return top; - } - } } \ No newline at end of file