This commit is contained in:
Sina Blattmann 2023-03-23 13:06:24 +01:00
commit b0aebf6e7d
17 changed files with 289 additions and 33 deletions

View File

@ -3,6 +3,7 @@
<ItemGroup>
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="MailKit" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
<PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.3" />
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
@ -26,14 +27,16 @@
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources" />
</ItemGroup>
<ItemGroup>
<None Update="Resources/s3cmd.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Update="Resources\urlAndKey.json">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -2,7 +2,6 @@ using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
using Microsoft.AspNetCore.Mvc;
namespace InnovEnergy.App.Backend.Controllers;
@ -17,11 +16,19 @@ public class Controller : ControllerBase
public ActionResult<Session> Login(String username, String password)
{
var user = Db.GetUserByName(username);
if (user is null || !user.VerifyPassword(password))
if (user is null)
return Unauthorized();
if (!(user.Password is null && user.MustResetPassword))
{
if (!user.VerifyPassword(password))
return Unauthorized();
}
var session = new Session(user);
//TODO The Frontend should check for the MustResetPassword Flag
return Db.Create(session)
? session
@ -106,7 +113,7 @@ public class Controller : ControllerBase
.Ancestors()
.SelectMany(f => f.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user))
.Select(u => new { folderId = f.Id, user = u }))
.Select(u => new { folderId = f.Id, folderName = f.Name, user = u }))
.ToList();
}
@ -144,7 +151,7 @@ public class Controller : ControllerBase
.Ancestors()
.SelectMany(f => f.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user))
.Select(u => new { folderId = f.Id, user = u }))
.Select(u => new { folderId = f.Id, folderName = f.Name, user = u }))
.ToList();
}
@ -317,6 +324,18 @@ public class Controller : ControllerBase
}
[HttpPut(nameof(UpdatePassword))]
public ActionResult<User> UpdatePassword(String newPassword, Token authToken)
{
var session = Db.GetSession(authToken);
return session.UpdatePassword(newPassword)
? Ok()
: Unauthorized();
}
[HttpPut(nameof(UpdateInstallation))]
public ActionResult<Installation> UpdateInstallation(Installation installation, Token authToken)
{
@ -340,6 +359,26 @@ public class Controller : ControllerBase
return folder;
}
[HttpPut(nameof(MoveInstallation))]
public ActionResult MoveInstallation(Int64 installationId,Int64 parentId, Token authToken)
{
var session = Db.GetSession(authToken);
return session.MoveInstallation(installationId, parentId)
? Ok()
: Unauthorized();
}
[HttpPut(nameof(MoveFolder))]
public ActionResult MoveFolder(Int64 folderId,Int64 parentId, Token authToken)
{
var session = Db.GetSession(authToken);
return session.MoveFolder(folderId, parentId)
? Ok()
: Unauthorized();
}
[HttpDelete(nameof(DeleteUser))]
public ActionResult DeleteUser(Int64 userId, Token authToken)
{

View File

@ -0,0 +1,6 @@
namespace InnovEnergy.App.Backend.DataTypes;
public class DeletedFolder : TreeNode {}
//Deleted Things need to have AT LEAST every property that normal things have
//Todo "Restore" Function? -k

View File

@ -0,0 +1,19 @@
namespace InnovEnergy.App.Backend.DataTypes;
//Deleted Things need to have AT LEAST every property that normal things have
public class DeletedInstallation : TreeNode
{
public String Location { get; set; } = "";
public String Region { get; set; } = "";
public String Country { get; set; } = "";
// TODO: make relation
public String OrderNumbers { get; set; } = "";
public Double Lat { get; set; }
public Double Long { get; set; }
public String S3Bucket { get; set; } = "";
public String S3Url { get; set; } = "";
}

View File

@ -0,0 +1,22 @@
using SQLite;
namespace InnovEnergy.App.Backend.DataTypes;
//Deleted Things need to have AT LEAST every property that normal things have
public class DeletedUser : TreeNode
{
public String Email { get; set; } = null!;
public Boolean HasWriteAccess { get; set; } = false;
public Boolean MustResetPassword { get; set; } = false;
public String Language { get; set; } = null!;
public String Password { get; set; } = null!;
[Unique]
public override String Name { get; set; } = null!;
// TODO: must reset pwd
}

View File

@ -23,6 +23,17 @@ public static class FolderMethods
.NotNull();
}
public static DeletedFolder ToDeletedFolder(this Folder folder)
{
var deletedFolder = new DeletedFolder();
foreach (var property in folder.GetType().GetProperties())
{
property.SetValue(deletedFolder,property.GetValue(folder));
}
return deletedFolder;
}
public static IEnumerable<User> UsersWithInheritedAccess(this Folder folder)
{
return folder

View File

@ -20,7 +20,8 @@ public static class InstallationMethods
}
public static async Task<Boolean> RenewS3BucketUrl(this Installation installation, TimeSpan validity)
{
{
installation.S3Url =
installation.S3Url = await S3Access.ReadOnly.SignUrl(installation.BucketName(), validity);
return Db.Update(installation);
}
@ -61,6 +62,17 @@ public static class InstallationMethods
.SelectMany(f => f.UsersWithDirectAccess())
.NotNull();
}
public static DeletedInstallation ToDeletedInstallation(this Installation installation)
{
var deletedInstallation = new DeletedInstallation();
foreach (var property in installation.GetType().GetProperties())
{
property.SetValue(deletedInstallation ,property.GetValue(installation));
}
return deletedInstallation;
}
public static IEnumerable<Folder> Ancestors(this Installation installation)
{

View File

@ -32,6 +32,38 @@ public static class SessionMethods
.WithParentOf(original) // prevent moving
.Apply(Db.Update);
}
public static Boolean MoveFolder(this Session? session, Int64 folderId, Int64 parentId)
{
var user = session?.User;
var folder = Db.GetFolderById(folderId);
var parent = Db.GetFolderById(parentId);
return user is not null
&& folder is not null
&& user.HasWriteAccess
&& user.HasAccessTo(folder)
&& user.HasAccessTo(parent)
&& folder
.Do(() => folder.ParentId = parentId)
.Apply(Db.Update);
}
public static Boolean MoveInstallation(this Session? session, Int64 installationId, Int64 parentId)
{
var user = session?.User;
var installation = Db.GetInstallationById(installationId);
var parent = Db.GetFolderById(parentId);
return user is not null
&& installation is not null
&& user.HasWriteAccess
&& user.HasAccessTo(installation)
&& user.HasAccessTo(parent)
&& installation
.Do(() => installation.ParentId = parentId)
.Apply(Db.Update);
}
public static Boolean Delete(this Session? session, Folder? folder)
{
@ -41,6 +73,7 @@ public static class SessionMethods
&& folder is not null
&& user.HasWriteAccess
&& user.HasAccessTo(folder)
&& Db.Create(folder.ToDeletedFolder())
&& Db.Delete(folder);
}
@ -84,21 +117,27 @@ public static class SessionMethods
&& installation is not null
&& user.HasWriteAccess
&& user.HasAccessTo(installation)
// && installation.DeleteBucket().Result // TODO
&& Db.Create(installation.ToDeletedInstallation())
// && installation.DeleteBucket().Result // TODO
&& Db.Delete(installation);
}
public static Boolean Create(this Session? session, User? newUser)
{
var sessionUser = session?.User;
return sessionUser is not null
&& newUser is not null
&& sessionUser.HasWriteAccess
&& newUser
.WithParent(sessionUser)
.Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password))
.Do(() => newUser.MustResetPassword = true)
.Apply(Db.Create)
&& Mailer.Mailer.SendVerificationMessage(newUser);
//Send Email to new user to verify email and set password
return sessionUser is not null
&& newUser is not null
&& sessionUser.HasWriteAccess
&& newUser
.WithParent(sessionUser)
.Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password))
.Apply(Db.Create);
}
public static Boolean Update(this Session? session, User? editedUser)
@ -116,6 +155,16 @@ public static class SessionMethods
.WithPasswordOf(originalUser)
.Apply(Db.Update);
}
public static Boolean UpdatePassword(this Session? session, String? newPassword)
{
var sessionUser = session?.User;
return sessionUser is not null
&& sessionUser
.Do(() => sessionUser.Password = sessionUser.SaltAndHashPassword(newPassword))
.Apply(Db.Update);
}
public static Boolean Delete(this Session? session, User? userToDelete)
{
@ -124,7 +173,8 @@ public static class SessionMethods
return sessionUser is not null
&& userToDelete is not null
&& sessionUser.HasWriteAccess
&& sessionUser.HasAccessTo(userToDelete)
&& sessionUser.HasAccessTo(userToDelete)
&& Db.Create(sessionUser.ToDeletedUser())
&& Db.Delete(userToDelete);
}

View File

@ -97,9 +97,10 @@ public static class UserMethods
public static Boolean VerifyPassword(this User user, String password)
{
return user.Password == user.SaltAndHashPassword(password);
return Db.GetUserWithPasswordByName(user.Name)?.Password == user.SaltAndHashPassword(password);
}
public static String SaltAndHashPassword(this User user, String password)
{
var dataToHash = $"{password}{user.Salt()}";
@ -183,10 +184,27 @@ public static class UserMethods
user.Password = other.Password;
return user;
}
public static DeletedUser ToDeletedUser(this User user)
{
var deletedUser = new DeletedUser();
foreach (var property in user.GetType().GetProperties())
{
property.SetValue(deletedUser,property.GetValue(user));
}
return deletedUser;
}
public static User? HidePassword(this User? user)
{
if(user is not null)
user.Password = "";
return user;
}
// TODO?
private static Boolean IsValidEmail(String email)
{

View File

@ -6,11 +6,15 @@ public class User : TreeNode
{
public String Email { get; set; } = null!;
public Boolean HasWriteAccess { get; set; } = false;
public Boolean MustResetPassword { get; set; } = false;
public String Language { get; set; } = null!;
public String Password { get; set; } = null!;
[Unique]
public override String Name { get; set; } = null!;
// TODO: must reset pwd
}

View File

@ -13,15 +13,30 @@ public static partial class Db
return Connection.Insert(installation) > 0;
}
public static Boolean Create(DeletedInstallation installation)
{
return Connection.Insert(installation) > 0;
}
public static Boolean Create(Folder folder)
{
return Connection.Insert(folder) > 0;
}
public static Boolean Create(DeletedFolder folder)
{
return Connection.Insert(folder) > 0;
}
public static Boolean Create(User user)
{
return Connection.Insert(user) > 0;
}
public static Boolean Create(DeletedUser user)
{
return Connection.Insert(user) > 0;
}
public static Boolean Create(Session session)
{

View File

@ -1,5 +1,7 @@
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.Database;
@ -21,14 +23,25 @@ public static partial class Db
public static User? GetUserById(Int64? id)
{
//TODO Sanitize Password here
return Users
.FirstOrDefault(u => u.Id == id);
.FirstOrDefault(u => u.Id == id)
.HidePassword();
}
public static User? GetUserByName(String userName)
{
//TODO Sanitize Password here
return Users
.FirstOrDefault(u => u.Name == userName);
.FirstOrDefault(u => u.Name == userName)
.HidePassword();
}
public static User? GetUserWithPasswordByName(String userName)
{
//TODO Sanitize Password here
return Users
.FirstOrDefault(u => u.Name == userName);
}
public static Session? GetSession(String token)

View File

@ -0,0 +1,35 @@
using System;
using InnovEnergy.App.Backend.DataTypes;
using MailKit.Net.Smtp;
using MailKit;
using MimeKit;
namespace InnovEnergy.App.Backend.Mailer;
public static class Mailer
{
public static Boolean SendVerificationMessage (User emailRecipientUser)
{
var email = new MimeMessage();
email.From.Add(new MailboxAddress("InnovEnergy", "noreply@innov.energy"));
email.To.Add(new MailboxAddress(emailRecipientUser.Name, "fern95@ethereal.email"));
email.Subject = "Create a new password for your Innovenergy-Account";
email.Body = new TextPart(MimeKit.Text.TextFormat.Plain) {
Text = "Dear " + emailRecipientUser.Name + "\n Please create a new password for your Innovenergy-account." +
"\n To do this just login at https://HEEEEELP"
};
using (var smtp = new SmtpClient())
{
smtp.Connect("smtp.ethereal.email", 587, false);
// Todo put me into urlAndKey.json
smtp.Authenticate("fern95@ethereal.email", "dYKVnc4RQNEFckHaNV");
smtp.Send(email);
smtp.Disconnect(true);
}
return true;
}
}

View File

@ -0,0 +1,6 @@
{
"ReadOnlyS3Key": "EXO44d2979c8e570eae81ead564",
"ReadOnlyS3Secret": "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU" ,
"ReadWriteS3Key": "EXO87ca85e29dd412f1238f1cf0",
"ReadWriteS3Secret": "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"
}

View File

@ -1,3 +1,6 @@
using static System.IO.File;
using static System.Text.Json.JsonSerializer;
namespace InnovEnergy.App.Backend.S3;
public static class S3Access
@ -6,16 +9,16 @@ public static class S3Access
// there so they can be changed without recompiling
// they should be read from disk on each use,
// so the backend does not need to be restarted on change
public static S3Cmd ReadOnly { get; } = new S3Cmd
(
key : "EXO44d2979c8e570eae81ead564",
secret: "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU"
key : Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadOnlyS3Key"],
secret: Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadOnlyS3Secret"]
);
public static S3Cmd ReadWrite { get; } = new S3Cmd
(
key : "EXO87ca85e29dd412f1238f1cf0",
secret: "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"
key : Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadWriteS3Key"],
secret: Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadWriteS3Secret"]
);
}

View File

@ -8,15 +8,15 @@ public class S3Cmd
{
private static readonly Command Python = Cli.Wrap("python3");
private const String S3CmdPath = "Resources/s3cmd.py";
private const String? S3CmdPath = "Resources/s3cmd.py";
private const String S3Prefix = "s3://";
private String[] DefaultArgs { get; }
private String?[] DefaultArgs { get; }
// ReSharper disable StringLiteralTypo
// ReSharper enable StringLiteralTypo
public S3Cmd(String key, String secret)
public S3Cmd(String? key, String? secret)
{
DefaultArgs = new[]
{

Binary file not shown.