improve backend
This commit is contained in:
parent
c4eb538132
commit
4c37c92f73
|
@ -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<String>]
|
||||
[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<User>]
|
||||
[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<User>]
|
||||
// [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<Installation>]
|
||||
// [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<Installation>]
|
||||
[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<Folder>]
|
||||
[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<Folder>]
|
||||
// [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<Installation[]>] // 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<Folder[]>] // 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<TreeNode[]>] // 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<TreeNode>(installations)
|
||||
.ToList(); // important!
|
||||
}
|
||||
// [Returns<Folder[]>] // 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<TreeNode[]>] // 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<TreeNode>;
|
||||
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<Int64>? hs = null)
|
||||
{
|
||||
// TODO: remove cycle detector
|
||||
hs ??= new HashSet<Int64>();
|
||||
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<TreeNode>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Controllers;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public record Credentials(String Username, String Password);
|
|
@ -0,0 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public record Credentials(String Username, String Password);
|
|
@ -0,0 +1,3 @@
|
|||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class Folder : TreeNode {}
|
|
@ -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; } = "";
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Folder> ChildFolders(this Folder parent)
|
||||
{
|
||||
return Db
|
||||
.Folders
|
||||
.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
public static IEnumerable<Installation> ChildInstallations(this Folder parent)
|
||||
{
|
||||
return Db
|
||||
.Installations
|
||||
.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
public static IEnumerable<Folder> 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<Folder> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
using InnovEnergy.App.Backend.Database;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
|
||||
|
||||
public static class InstallationMethods
|
||||
{
|
||||
public static IEnumerable<Folder> Ancestors(this Installation installation)
|
||||
{
|
||||
var parentFolder = Parent(installation);
|
||||
|
||||
return parentFolder is null
|
||||
? Enumerable.Empty<Folder>()
|
||||
: 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Installation> AccessibleInstallations(this User user)
|
||||
{
|
||||
var direct = user.DirectlyAccessibleInstallations();
|
||||
var fromFolders = user
|
||||
.AccessibleFolders()
|
||||
.SelectMany(u => u.ChildInstallations());
|
||||
|
||||
return direct
|
||||
.Concat(fromFolders)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public static IEnumerable<Folder> 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<TreeNode> AccessibleFoldersAndInstallations(this User user)
|
||||
{
|
||||
var folders = user.AccessibleFolders() as IEnumerable<TreeNode>;
|
||||
var installations = user.AccessibleInstallations();
|
||||
|
||||
return folders.Concat(installations);
|
||||
}
|
||||
|
||||
public static IEnumerable<Installation> 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<Folder> 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<User> ChildUsers(this User parent)
|
||||
{
|
||||
return Db
|
||||
.Users
|
||||
.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
public static IEnumerable<User> 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<User> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<TreeNode>? Children { get; set; }
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Session> Sessions => Connection.Table<Session>();
|
||||
public static TableQuery<Folder> Folders => Connection.Table<Folder>();
|
||||
|
@ -18,149 +22,55 @@ public static partial class Db
|
|||
public static TableQuery<User> Users => Connection.Table<User>();
|
||||
public static TableQuery<User2Folder> User2Folder => Connection.Table<User2Folder>();
|
||||
public static TableQuery<User2Installation> User2Installation => Connection.Table<User2Installation>();
|
||||
|
||||
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<User>();
|
||||
db.CreateTable<Installation>();
|
||||
db.CreateTable<Folder>();
|
||||
db.CreateTable<User2Folder>();
|
||||
db.CreateTable<User2Installation>();
|
||||
db.CreateTable<Session>();
|
||||
Connection.CreateTable<User>();
|
||||
Connection.CreateTable<Installation>();
|
||||
Connection.CreateTable<Folder>();
|
||||
Connection.CreateTable<User2Folder>();
|
||||
Connection.CreateTable<User2Installation>();
|
||||
Connection.CreateTable<Session>();
|
||||
});
|
||||
|
||||
|
||||
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<Boolean> 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<Installation> GetAllAccessibleInstallations(User user)
|
||||
{
|
||||
var direct = GetDirectlyAccessibleInstallations(user);
|
||||
var fromFolders = GetAllAccessibleFolders(user)
|
||||
.SelectMany(GetChildInstallations);
|
||||
|
||||
return direct
|
||||
.Concat(fromFolders)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public static IEnumerable<Folder> 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<Installation> 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<Folder> 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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<Folder> GetChildFolders(this Folder parent)
|
||||
{
|
||||
return Folders.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
public static IEnumerable<Installation> GetChildInstallations(this Folder parent)
|
||||
{
|
||||
return Installations.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
public static IEnumerable<Folder> 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<Folder> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
using InnovEnergy.App.Backend.Model;
|
||||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
public static partial class Db
|
||||
{
|
||||
public static IEnumerable<Folder> Ancestors(this Installation installation)
|
||||
{
|
||||
var parentFolder = GetParent(installation);
|
||||
|
||||
return parentFolder is null
|
||||
? Enumerable.Empty<Folder>()
|
||||
: 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<User> GetChildUsers(this User parent)
|
||||
{
|
||||
return Users
|
||||
.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
public static IEnumerable<User> 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<User> 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;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace InnovEnergy.App.Backend.Model;
|
||||
|
||||
public class Folder : TreeNode
|
||||
{
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Model.Relations;
|
||||
|
||||
public class Session : Relation<String, Int64>
|
||||
{
|
||||
[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);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Model.Relations;
|
||||
|
||||
internal class User2Folder : Relation<Int64, Int64>
|
||||
{
|
||||
[Indexed] public Int64 UserId { get => Left ; set => Left = value;}
|
||||
[Indexed] public Int64 FolderId { get => Right; set => Right = value;}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Model.Relations;
|
||||
|
||||
internal class User2Installation : Relation<Int64, Int64>
|
||||
{
|
||||
[Indexed] public Int64 UserId { get => Left ; set => Left = value;}
|
||||
[Indexed] public Int64 InstallationId { get => Right; set => Right = value;}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<L,R>
|
||||
{
|
|
@ -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<String, Int64>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Relations;
|
||||
|
||||
public class User2Folder : Relation<Int64, Int64>
|
||||
{
|
||||
[Indexed] public Int64 UserId { get => Left ; init => Left = value;}
|
||||
[Indexed] public Int64 FolderId { get => Right; init => Right = value;}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Relations;
|
||||
|
||||
public class User2Installation : Relation<Int64, Int64>
|
||||
{
|
||||
[Indexed] public Int64 UserId { get => Left ; init => Left = value;}
|
||||
[Indexed] public Int64 InstallationId { get => Right; init => Right = value;}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue