using System.Net; using System.Text; using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Model; using InnovEnergy.App.Backend.Model.Relations; using InnovEnergy.App.Backend.Utils; using InnovEnergy.Lib.Utils; using Microsoft.AspNetCore.Mvc; using HttpContextAccessor = Microsoft.AspNetCore.Http.HttpContextAccessor; namespace InnovEnergy.App.Backend.Controllers; [ApiController] [Route("api/")] public class Controller { [Returns] [Returns(HttpStatusCode.Unauthorized)] [Returns(HttpStatusCode.BadRequest)] [HttpPost($"{nameof(Login)}")] public Object Login(Credentials credentials) { if (String.IsNullOrWhiteSpace(credentials.Username) || String.IsNullOrWhiteSpace(credentials.Password)) return new HttpResponseMessage(HttpStatusCode.BadRequest); using var db = Db.Connect(); var user = db.GetUserByEmail(credentials.Username); if (user is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); #if !DEBUG if (!VerifyPassword(credentials.Password, user)) return new HttpResponseMessage(HttpStatusCode.Unauthorized); #endif var ses = new Session(user); db.NewSession(ses); return ses.Token; } [Returns(HttpStatusCode.OK)] [Returns(HttpStatusCode.Unauthorized)] [HttpPost($"{nameof(Logout)}")] public Object Logout() { var caller = GetCaller(); 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)] [HttpPost($"{nameof(UpdateS3Credentials)}")] public Object UpdateS3Credentials() { // TODO: S3Credentials should be per session, not per user var caller = GetCaller(); if (caller is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); using var db = Db.Connect(); return db.CreateAndSaveUserS3ApiKey(caller); } [Returns] [Returns(HttpStatusCode.Unauthorized)] [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(); 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); 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)] [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] // assuming swagger knows about arrays but not lists (JSON) [Returns(HttpStatusCode.Unauthorized)] [HttpGet($"{nameof(GetAllInstallations)}/")] public Object GetAllInstallations() { var caller = GetCaller(); if (caller == null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); using var db = Db.Connect(); return db .GetAllAccessibleInstallations(caller) .ToList(); // important! } [Returns] // assuming swagger knows about arrays but not lists (JSON) [Returns(HttpStatusCode.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! } [Returns] // assuming swagger knows about arrays but not lists (JSON) [Returns(HttpStatusCode.Unauthorized)] [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) .Do(f => f.ParentId = 0) // ReSharper disable once AccessToDisposedClosure .Select(f => PopulateChildren(db, f)); var installations = db.GetDirectlyAccessibleInstallations(caller); return folders .Concat(installations) .ToList(); // important! } 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)] [HttpPut($"{nameof(UpdateUser)}/")] public Object UpdateUser(User updatedUser) { // TODO: distinguish between create and update var caller = GetCaller(); if (caller == null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); using var db = Db.Connect(); return db.GetUserById(updatedUser.Id) != null ? db.UpdateUser(updatedUser) : db.CreateUser(updatedUser); } [Returns(HttpStatusCode.OK)] [Returns(HttpStatusCode.Unauthorized)] [HttpPut($"{nameof(UpdateInstallation)}/")] public Object UpdateInstallation(Installation installation) { var caller = GetCaller(); if (caller is null || !caller.HasWriteAccess) return new HttpResponseMessage(HttpStatusCode.Unauthorized); using var db = Db.Connect(); var hasAccessToInstallation = db .GetAllAccessibleInstallations(caller) .Any(i => i.Id == installation.Id); if (!hasAccessToInstallation) return new HttpResponseMessage(HttpStatusCode.Unauthorized); // TODO: accessibility by other users etc // TODO: sanity check changes return db.UpdateInstallation(installation); } [Returns(HttpStatusCode.OK)] [Returns(HttpStatusCode.Unauthorized)] [HttpPut($"{nameof(UpdateFolder)}/")] public Object UpdateFolder(Folder folder) { var caller = GetCaller(); if (caller is null || !caller.HasWriteAccess) return new HttpResponseMessage(HttpStatusCode.Unauthorized); using var db = Db.Connect(); var hasAccessToFolder = db .GetAllAccessibleFolders(caller) .Any(f => f.Id == folder.Id); if (!hasAccessToFolder) return new HttpResponseMessage(HttpStatusCode.Unauthorized); // TODO: accessibility by other users etc // TODO: sanity check changes return db.UpdateFolder(folder); } [Returns(HttpStatusCode.OK)] [Returns(HttpStatusCode.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); if (userToBeDeleted is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); return db.DeleteUser(userToBeDeleted); } [Returns(HttpStatusCode.OK)] [Returns(HttpStatusCode.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 installationToBeDeleted = db .GetAllAccessibleInstallations(caller) .FirstOrDefault(i => i.Id == installationId); if (installationToBeDeleted is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); return db.DeleteInstallation(installationToBeDeleted); } [ProducesResponseType(200)] [ProducesResponseType(401)] [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 folderToDelete = db .GetAllAccessibleFolders(caller) .FirstOrDefault(f => f.Id == folderId); if (folderToDelete is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); return db.DeleteFolder(folderToDelete); } private static User? GetCaller() { var ctxAccessor = new HttpContextAccessor(); return ctxAccessor.HttpContext?.Items["User"] as User; } 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; } }