Added deletedFolders/installations/users for archive purposes, login is now possible without pw if no pw is set and you need to reset your pw, user hased pws should no longer be given out by the backend, moving folders and installations now have their own calls, loading s3 keys from disk on use

This commit is contained in:
Kim 2023-03-23 12:47:25 +01:00
parent 656b671962
commit 7d4309f3af
17 changed files with 287 additions and 31 deletions

View File

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

View File

@ -2,7 +2,6 @@ using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods; using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations; using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace InnovEnergy.App.Backend.Controllers; namespace InnovEnergy.App.Backend.Controllers;
@ -18,11 +17,19 @@ public class Controller : ControllerBase
{ {
var user = Db.GetUserByName(username); var user = Db.GetUserByName(username);
if (user is null || !user.VerifyPassword(password)) if (user is null)
return Unauthorized(); return Unauthorized();
if (!(user.Password is null && user.MustResetPassword))
{
if (!user.VerifyPassword(password))
return Unauthorized();
}
var session = new Session(user); var session = new Session(user);
//TODO The Frontend should check for the MustResetPassword Flag
return Db.Create(session) return Db.Create(session)
? session ? session
: Unauthorized(); : Unauthorized();
@ -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))] [HttpPut(nameof(UpdateInstallation))]
public ActionResult<Installation> UpdateInstallation(Installation installation, Token authToken) public ActionResult<Installation> UpdateInstallation(Installation installation, Token authToken)
{ {
@ -340,6 +359,26 @@ public class Controller : ControllerBase
return folder; 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))] [HttpDelete(nameof(DeleteUser))]
public ActionResult DeleteUser(Int64 userId, Token authToken) 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(); .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) public static IEnumerable<User> UsersWithInheritedAccess(this Folder folder)
{ {
return folder return folder

View File

@ -21,6 +21,7 @@ public static class InstallationMethods
public static async Task<Boolean> RenewS3BucketUrl(this Installation installation, TimeSpan validity) public static async Task<Boolean> RenewS3BucketUrl(this Installation installation, TimeSpan validity)
{ {
installation.S3Url =
installation.S3Url = await S3Access.ReadOnly.SignUrl(installation.BucketName(), validity); installation.S3Url = await S3Access.ReadOnly.SignUrl(installation.BucketName(), validity);
return Db.Update(installation); return Db.Update(installation);
} }
@ -62,6 +63,17 @@ public static class InstallationMethods
.NotNull(); .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) public static IEnumerable<Folder> Ancestors(this Installation installation)
{ {
var parentFolder = Parent(installation); var parentFolder = Parent(installation);

View File

@ -33,6 +33,38 @@ public static class SessionMethods
.Apply(Db.Update); .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) public static Boolean Delete(this Session? session, Folder? folder)
{ {
var user = session?.User; var user = session?.User;
@ -41,6 +73,7 @@ public static class SessionMethods
&& folder is not null && folder is not null
&& user.HasWriteAccess && user.HasWriteAccess
&& user.HasAccessTo(folder) && user.HasAccessTo(folder)
&& Db.Create(folder.ToDeletedFolder())
&& Db.Delete(folder); && Db.Delete(folder);
} }
@ -84,7 +117,8 @@ public static class SessionMethods
&& installation is not null && installation is not null
&& user.HasWriteAccess && user.HasWriteAccess
&& user.HasAccessTo(installation) && user.HasAccessTo(installation)
// && installation.DeleteBucket().Result // TODO && Db.Create(installation.ToDeletedInstallation())
// && installation.DeleteBucket().Result // TODO
&& Db.Delete(installation); && Db.Delete(installation);
} }
@ -93,12 +127,17 @@ public static class SessionMethods
var sessionUser = session?.User; var sessionUser = session?.User;
return sessionUser is not null return sessionUser is not null
&& newUser is not null && newUser is not null
&& sessionUser.HasWriteAccess && sessionUser.HasWriteAccess
&& newUser && newUser
.WithParent(sessionUser) .WithParent(sessionUser)
.Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password)) .Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password))
.Apply(Db.Create); .Do(() => newUser.MustResetPassword = true)
.Apply(Db.Create)
&& Mailer.Mailer.SendVerificationMessage(newUser);
//Send Email to new user to verify email and set password
} }
public static Boolean Update(this Session? session, User? editedUser) public static Boolean Update(this Session? session, User? editedUser)
@ -117,6 +156,16 @@ public static class SessionMethods
.Apply(Db.Update); .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) public static Boolean Delete(this Session? session, User? userToDelete)
{ {
var sessionUser = session?.User; var sessionUser = session?.User;
@ -125,6 +174,7 @@ public static class SessionMethods
&& userToDelete is not null && userToDelete is not null
&& sessionUser.HasWriteAccess && sessionUser.HasWriteAccess
&& sessionUser.HasAccessTo(userToDelete) && sessionUser.HasAccessTo(userToDelete)
&& Db.Create(sessionUser.ToDeletedUser())
&& Db.Delete(userToDelete); && Db.Delete(userToDelete);
} }

View File

@ -97,9 +97,10 @@ public static class UserMethods
public static Boolean VerifyPassword(this User user, String password) 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) public static String SaltAndHashPassword(this User user, String password)
{ {
var dataToHash = $"{password}{user.Salt()}"; var dataToHash = $"{password}{user.Salt()}";
@ -184,7 +185,24 @@ public static class UserMethods
return user; 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? // TODO?

View File

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

View File

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

View File

@ -1,5 +1,7 @@
using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations; using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend.Database; namespace InnovEnergy.App.Backend.Database;
@ -21,14 +23,25 @@ public static partial class Db
public static User? GetUserById(Int64? id) public static User? GetUserById(Int64? id)
{ {
//TODO Sanitize Password here
return Users return Users
.FirstOrDefault(u => u.Id == id); .FirstOrDefault(u => u.Id == id)
.HidePassword();
} }
public static User? GetUserByName(String userName) public static User? GetUserByName(String userName)
{ {
//TODO Sanitize Password here
return Users 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) 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; namespace InnovEnergy.App.Backend.S3;
public static class S3Access public static class S3Access
@ -9,13 +12,13 @@ public static class S3Access
public static S3Cmd ReadOnly { get; } = new S3Cmd public static S3Cmd ReadOnly { get; } = new S3Cmd
( (
key : "EXO44d2979c8e570eae81ead564", key : Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadOnlyS3Key"],
secret: "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU" secret: Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadOnlyS3Secret"]
); );
public static S3Cmd ReadWrite { get; } = new S3Cmd public static S3Cmd ReadWrite { get; } = new S3Cmd
( (
key : "EXO87ca85e29dd412f1238f1cf0", key : Deserialize<Dictionary<String, String>>(OpenRead("./Resources/urlAndKey.json"))?["ReadWriteS3Key"],
secret: "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU" 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 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 const String S3Prefix = "s3://";
private String[] DefaultArgs { get; } private String?[] DefaultArgs { get; }
// ReSharper disable StringLiteralTypo // ReSharper disable StringLiteralTypo
// ReSharper enable StringLiteralTypo // ReSharper enable StringLiteralTypo
public S3Cmd(String key, String secret) public S3Cmd(String? key, String? secret)
{ {
DefaultArgs = new[] DefaultArgs = new[]
{ {

Binary file not shown.