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> <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;
@ -17,11 +16,19 @@ public class Controller : ControllerBase
public ActionResult<Session> Login(String username, String password) public ActionResult<Session> Login(String username, String password)
{ {
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
@ -106,7 +113,7 @@ public class Controller : ControllerBase
.Ancestors() .Ancestors()
.SelectMany(f => f.UsersWithDirectAccess() .SelectMany(f => f.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user)) .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(); .ToList();
} }
@ -144,7 +151,7 @@ public class Controller : ControllerBase
.Ancestors() .Ancestors()
.SelectMany(f => f.UsersWithDirectAccess() .SelectMany(f => f.UsersWithDirectAccess()
.Where(u => u.IsDescendantOf(user)) .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(); .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))] [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

@ -20,7 +20,8 @@ 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);
} }
@ -61,6 +62,17 @@ public static class InstallationMethods
.SelectMany(f => f.UsersWithDirectAccess()) .SelectMany(f => f.UsersWithDirectAccess())
.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)
{ {

View File

@ -32,6 +32,38 @@ public static class SessionMethods
.WithParentOf(original) // prevent moving .WithParentOf(original) // prevent moving
.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)
{ {
@ -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,21 +117,27 @@ 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);
} }
public static Boolean Create(this Session? session, User? newUser) public static Boolean Create(this Session? session, User? newUser)
{ {
var sessionUser = session?.User; 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) public static Boolean Update(this Session? session, User? editedUser)
@ -116,6 +155,16 @@ public static class SessionMethods
.WithPasswordOf(originalUser) .WithPasswordOf(originalUser)
.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)
{ {
@ -124,7 +173,8 @@ public static class SessionMethods
return sessionUser is not null return sessionUser is not null
&& 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()}";
@ -183,10 +184,27 @@ public static class UserMethods
user.Password = other.Password; user.Password = other.Password;
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?
private static Boolean IsValidEmail(String email) private static Boolean IsValidEmail(String email)
{ {

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,15 +13,30 @@ 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)
{ {

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
@ -6,16 +9,16 @@ public static class S3Access
// there so they can be changed without recompiling // there so they can be changed without recompiling
// they should be read from disk on each use, // they should be read from disk on each use,
// so the backend does not need to be restarted on change // so the backend does not need to be restarted on change
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.