Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

Submodule Projekte/WebDesktop 2.0 added at 15f48d259f

3
Projekte/WebDesktop/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.vs
.vscode
UploadData

View File

@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.WebDesktop.iml
/modules.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="WebDesktop" uuid="e24e2ab8-b675-4c92-a735-b4c27afafc3f">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://leon-hoppe.de:3306/WebDesktop</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectDictionaryState">
<dictionary name="leon" />
</component>
</project>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders>
<Path>WebDesktopFrontend</Path>
<Path>WindowAPI</Path>
</attachedFolders>
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<enabledExtensions>
<entry key="MermaidLanguageExtension" value="false" />
<entry key="PlantUMLLanguageExtension" value="false" />
</enabledExtensions>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/WebDesktopBackend/Startup.cs" dialect="GenericSQL" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

View File

@@ -0,0 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebDesktopBackend", "WebDesktopBackend/WebDesktopBackend.csproj", "{ECE6CBFE-02CC-4A54-BE36-57226B1B2357}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ECE6CBFE-02CC-4A54-BE36-57226B1B2357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECE6CBFE-02CC-4A54-BE36-57226B1B2357}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECE6CBFE-02CC-4A54-BE36-57226B1B2357}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECE6CBFE-02CC-4A54-BE36-57226B1B2357}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@@ -0,0 +1,3 @@
/obj
/bin
/Uploads

View File

@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/.idea.WebDesktopBackend.iml
/modules.xml
/contentModel.xml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="WebDesktop@213.136.89.237" uuid="7571b012-f7f2-4a6f-89f5-9a493192589b">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://213.136.89.237:3306/WebDesktop</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/Persistance/TokenRepository.cs" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/Persistance/UserRepository.cs" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/Startup.cs" dialect="GenericSQL" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,23 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using WebDesktopBackend.Entitys.Files;
using WebDesktopBackend.LogicResults;
using FileShare = WebDesktopBackend.Entitys.Files.FileShare;
namespace WebDesktopBackend.Contract.Logic {
public interface IFileLogic {
ILogicResult CreateDirectory(string directory, string name);
Task<ILogicResult> UploadFile(IFormCollection data);
Task<ILogicResult> UploadJson(string directory, string name, string content);
ILogicResult<FileStream> DownloadFile(string directory, string file);
Task<ILogicResult<string>> DownloadJson(string file);
ILogicResult<DirectoryContent> GetDirectory(string directory);
ILogicResult<DirectoryInformation> GetDirectoryInformation(string directory);
ILogicResult<FileInformation> GetFileInformation(string directory, string file);
ILogicResult MoveDirectory(string directory, string name, string to);
ILogicResult MoveFile(string directory, string file, string to);
ILogicResult Delete(string url);
ILogicResult<FileShare> Share(string url);
}
}

View File

@@ -0,0 +1,22 @@
using WebDesktopBackend.Entitys.Tokens;
using WebDesktopBackend.Entitys.User;
using WebDesktopBackend.LogicResults;
namespace WebDesktopBackend.Contract.Logic {
public interface IUserLogic {
ILogicResult<Tokens> Login(UserLogin login);
ILogicResult<Tokens> Register(UserEditor editor);
ILogicResult Logout();
ILogicResult EditUser(string id, UserEditor editor);
ILogicResult DeleteUser(string id);
ILogicResult<User> GetUser(string id);
ILogicResult<User[]> GetUsers();
ILogicResult Valdiate();
ILogicResult<AccessTokenResponse> GetToken(string refreshTokenId);
ILogicResult<User> GetOwnUser();
ILogicResult<string[]> GetPermissions(string id);
ILogicResult<string[]> GetRawPermissions(string id);
ILogicResult AddPermission(string id, string permission);
ILogicResult DeletePermission(string id, string permission);
}
}

View File

@@ -0,0 +1,12 @@
namespace WebDesktopBackend.Contract {
public class Permissions {
// --- USERS ---
public const string ShowUsers = "users.show";
public const string EditUsers = "users.edit";
public const string DeleteUsers = "users.delete";
public const string EditUserPermissions = "users.permissions";
// --- PROGRAMS ---
public const string UseAdminProgram = "app.admin.use";
}
}

View File

@@ -0,0 +1,23 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using WebDesktopBackend.Entitys.Files;
namespace WebDesktopBackend.Contract.Persistance {
public interface IFileRepository {
void InitUser(string userId);
void DeleteUserFolder(string userId);
bool CreateDirectory(string directory, string name);
Task UploadFile(IFormFile file, string directory);
Task UploadJson(string directory, string name, string data);
FileStream DownloadFile(string path);
Task<string> DownloadJson(string file);
DirectoryContent GetDirectory(string directory);
DirectoryInformation GetDirectoryInformation(string directory);
FileInformation GetFileInformation(string file);
void MoveDirectory(string directory, string to);
void MoveFile(string file, string to);
void Delete(string url);
string GenerateShareId(string url, string owner);
}
}

View File

@@ -0,0 +1,10 @@
using WebDesktopBackend.Entitys.Permissions;
namespace WebDesktopBackend.Contract.Persistance {
public interface IGroupRepository {
PermissionGroup GetPermissionGroup(string name);
PermissionGroup[] GetGroupsFromUser(string userId);
PermissionGroup[] ExtractGroups(Permission[] permissions);
Permission[] GetUserPermissions(string id);
}
}

View File

@@ -0,0 +1,18 @@
using WebDesktopBackend.Entitys.Permissions;
using WebDesktopBackend.Entitys.Tokens;
namespace WebDesktopBackend.Contract.Persistance {
public interface ITokenRepository {
RefreshToken GetRefreshToken(string id);
AccessToken GetAccessToken(string id);
bool ValidateAccessToken(string id);
bool ValidateRefreshToken(string id);
RefreshToken CreateRefreshToken(string userId);
AccessToken CreateAccessToken(string refreshTokenId);
void DeleteUserTokens(string id);
void DeleteRefreshToken(string id);
Permission[] GetUserPermissions(string id);
void AddPermission(string id, string permission);
void DeletePermission(string id, string permission);
}
}

View File

@@ -0,0 +1,15 @@
using WebDesktopBackend.Entitys.User;
namespace WebDesktopBackend.Contract.Persistance {
public interface IUserRepository {
User AddUser(UserEditor editor);
void EditUser(string id, UserEditor editor);
void DeleteUser(string id);
User GetUser(string id);
User GetUserByUsername(string username);
User GetUserByEmail(string email);
User GetUserFromLogin(UserLogin login);
User[] GetUsers();
bool Login(UserLogin login);
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebDesktopBackend.Contract.Logic;
using WebDesktopBackend.Entitys.Files;
using WebDesktopBackend.LogicResults;
using WebDesktopBackend.Security.Authorization;
using FileShare = WebDesktopBackend.Entitys.Files.FileShare;
namespace WebDesktopBackend.Controller {
[ApiController]
[Route("files")]
public class FileController : ControllerBase {
private readonly IFileLogic _fileLogic;
public FileController(IFileLogic fileLogic) {
_fileLogic = fileLogic;
}
[HttpPost("upload/directory")]
[Authorized]
public ActionResult CreateDirectory([FromQuery] string directory, [FromQuery] string name) {
return this.FromLogicResult(_fileLogic.CreateDirectory(directory, name));
}
[HttpPost("upload/file")]
[Authorized]
[DisableRequestSizeLimit]
public async Task<ActionResult> UploadFile() {
try {
return this.FromLogicResult(await _fileLogic.UploadFile(Request.Form));
} catch (Exception) {
return StatusCode((int)HttpStatusCode.BadRequest, "File upload Interupted");
}
}
[HttpPost("upload/json")]
[Authorized]
[DisableRequestSizeLimit]
public async Task<ActionResult> UploadJson([FromQuery] string directory, [FromQuery] string name) {
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
string content = await reader.ReadToEndAsync();
return this.FromLogicResult(await _fileLogic.UploadJson(directory, name, content));
}
[HttpGet("download/file")]
[Authorized]
public IActionResult DownloadFile([FromQuery] string directory, [FromQuery] string file) {
var result = _fileLogic.DownloadFile(directory, file);
if (!result.IsSuccessful)
return this.FromLogicResult(result);
return File(result.Data, "APPLICATION/octet-stream", file);
}
[HttpGet("download/json")]
[Authorized]
public async Task<ActionResult<string>> DownloadJson([FromQuery] string file) {
return this.FromLogicResult(await _fileLogic.DownloadJson(file));
}
[HttpGet("content")]
[Authorized]
public ActionResult<DirectoryContent> GetDirectoryContent([FromQuery] string directory) {
return this.FromLogicResult(_fileLogic.GetDirectory(directory));
}
[HttpGet("info/directory")]
[Authorized]
public ActionResult<DirectoryInformation> GetDirectoryInformation([FromQuery] string directory) {
return this.FromLogicResult(_fileLogic.GetDirectoryInformation(directory));
}
[HttpGet("info/file")]
[Authorized]
public ActionResult<DirectoryInformation> GetFileInformation([FromQuery] string directory, [FromQuery] string file) {
return this.FromLogicResult(_fileLogic.GetFileInformation(directory, file));
}
[HttpPut("move/directory")]
[Authorized]
public ActionResult MoveDirectory([FromQuery] string directory, [FromQuery] string name, [FromQuery] string to) {
return this.FromLogicResult(_fileLogic.MoveDirectory(directory, name, to));
}
[HttpPut("move/file")]
[Authorized]
public ActionResult MoveFile([FromQuery] string directory, [FromQuery] string file, [FromQuery] string to) {
return this.FromLogicResult(_fileLogic.MoveFile(directory, file, to));
}
[HttpDelete("delete")]
[Authorized]
public ActionResult DeleteFile([FromQuery] string url) {
return this.FromLogicResult(_fileLogic.Delete(url));
}
[HttpGet("share")]
[Authorized]
public ActionResult<FileShare> ShareFile([FromQuery] string url) {
return this.FromLogicResult(_fileLogic.Share(url));
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WebDesktopBackend.Extentions;
using WebDesktopBackend.Security.Authorization;
namespace WebDesktopBackend.Controller {
[ApiController]
[Route("update")]
public class UpdateController : ControllerBase {
[HttpGet("test")]
public ActionResult Test() {
return Ok("Authorized");
}
[HttpGet]
[Authorized("group.admin")]
public async Task Update() {
if (HttpContext.WebSockets.IsWebSocketRequest) {
using var socket = await HttpContext.WebSockets.AcceptWebSocketAsync();
using var target = await new ClientWebSocket().ConnectAsync(new Uri("ws://213.136.89.237:4042"));
var t1 = socket.AddMessageEventHandler(msg => {
target.SendMessage(msg);
});
var t2 = target.AddMessageEventHandler(msg => {
socket.SendMessage(msg);
});
while (!socket.CloseStatus.HasValue) {
await Task.Delay(500);
}
t1.Cancel();
t2.Cancel();
await target.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
} else {
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WebDesktopBackend.Contract;
using WebDesktopBackend.Contract.Logic;
using WebDesktopBackend.Entitys.Tokens;
using WebDesktopBackend.Entitys.User;
using WebDesktopBackend.LogicResults;
using WebDesktopBackend.Security;
using WebDesktopBackend.Security.Authorization;
namespace WebDesktopBackend.Controller {
[ApiController]
[Route("users")]
public class UserController : ControllerBase {
private readonly IUserLogic _logic;
private readonly ITokenContext _context;
public UserController(IUserLogic logic, ITokenContext context) {
_logic = logic;
_context = context;
}
[HttpPut("login")]
public ActionResult<AccessTokenResponse> Login([FromBody] UserLogin login) {
ILogicResult<Tokens> result = _logic.Login(login);
if (result.State == LogicResultState.Ok) SetRefreshToken(result.Data.refreshToken);
return this.FromLogicResult(new LogicResult<AccessTokenResponse> {State = result.State, Data = new AccessTokenResponse {Id = result.Data?.accessToken.Id}});
}
[HttpPost("register")]
public ActionResult<AccessTokenResponse> Register([FromBody] UserEditor editor) {
ILogicResult<Tokens> result = _logic.Register(editor);
SetRefreshToken(result.Data.refreshToken);
return this.FromLogicResult(new LogicResult<AccessTokenResponse> {State = result.State, Data = new AccessTokenResponse {Id = result.Data.accessToken.Id}});
}
[HttpDelete("logout")]
[Authorized]
public ActionResult Logout() {
DeleteRefreshToken();
return this.FromLogicResult(_logic.Logout());
}
[HttpPut("{id}")]
[Authorized(Permissions.EditUsers)]
public ActionResult EditUser(string id, [FromBody] UserEditor editor) {
return this.FromLogicResult(_logic.EditUser(id, editor));
}
[HttpDelete("{id}")]
[Authorized(Permissions.DeleteUsers)]
public ActionResult DeleteUser(string id) {
return this.FromLogicResult(_logic.DeleteUser(id));
}
[HttpGet("{id}")]
[Authorized(Permissions.ShowUsers)]
public ActionResult<User> GetUser(string id) {
return this.FromLogicResult(_logic.GetUser(id));
}
[HttpGet]
[Authorized(Permissions.ShowUsers)]
public ActionResult<User[]> GetUsers() {
return this.FromLogicResult(_logic.GetUsers());
}
[HttpGet("validate")]
[Authorized]
public ActionResult Validate() {
return this.FromLogicResult(_logic.Valdiate());
}
[HttpGet("token")]
public ActionResult<AccessTokenResponse> GetToken() {
return this.FromLogicResult(_logic.GetToken(GetRefreshToken()));
}
[HttpGet("ownuser")]
[Authorized]
public ActionResult<User> GetOwnUser() {
return this.FromLogicResult(_logic.GetOwnUser());
}
[HttpPut("ownuser")]
[Authorized]
public ActionResult<User> EditOwnUser([FromBody] UserEditor editor) {
return this.FromLogicResult(_logic.EditUser(_context.UserId, editor));
}
[HttpDelete("ownuser")]
[Authorized]
public ActionResult<User> DeleteOwnUser() {
Logout();
return this.FromLogicResult(_logic.DeleteUser(_context.UserId));
}
[HttpGet("{id}/permissions")]
[Authorized(Permissions.EditUserPermissions)]
public ActionResult<string[]> GetPermissions(string id) {
return this.FromLogicResult(_logic.GetPermissions(id));
}
[HttpGet("{id}/permissions/raw")]
[Authorized(Permissions.EditUserPermissions)]
public ActionResult<string[]> GetRawPermissions(string id) {
return this.FromLogicResult(_logic.GetRawPermissions(id));
}
[HttpGet("permissions")]
[Authorized]
public ActionResult<string[]> GetPermissions() {
return this.FromLogicResult(_logic.GetPermissions(HttpContext.User.GetUserId()));
}
[HttpPost("{id}/permissions/{permission}")]
[Authorized(Permissions.EditUserPermissions)]
public ActionResult AddPermission(string id, string permission) {
return this.FromLogicResult(_logic.AddPermission(id, permission));
}
[HttpDelete("{id}/permissions/{permission}")]
[Authorized(Permissions.EditUserPermissions)]
public ActionResult DeletePermission(string id, string permission) {
return this.FromLogicResult(_logic.DeletePermission(id, permission));
}
private void DeleteRefreshToken()
{
HttpContext.Response.Cookies.Delete("refresh_token");
}
private void SetRefreshToken(RefreshToken token)
{
HttpContext.Response.Cookies.Append("refresh_token", token.Id, new CookieOptions()
{
MaxAge = token.ExpirationDate - DateTime.Now,
HttpOnly = true,
Secure = true
});
}
private string GetRefreshToken() {
return HttpContext.Request.Cookies["refresh_token"];
}
}
}

View File

@@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore;
using WebDesktopBackend.Entitys.Files;
using WebDesktopBackend.Entitys.Permissions;
using WebDesktopBackend.Entitys.Tokens;
using WebDesktopBackend.Entitys.User;
namespace WebDesktopBackend {
public class DatabaseContext : DbContext {
public DbSet<User> Users { get; set; }
public DbSet<RefreshToken> RefreshTokens { get; set; }
public DbSet<AccessToken> AccessTokens { get; set; }
public DbSet<Permission> Permissions { get; set; }
public DbSet<FileShare> FileShares { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseMySQL(MySql.ConnectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>(entry => {
entry.HasKey(e => e.Id);
entry.Property(e => e.FirstName);
entry.Property(e => e.LastName);
entry.Property(e => e.Email);
entry.Property(e => e.Username);
entry.Property(e => e.Password);
entry.Property(e => e.Created);
});
modelBuilder.Entity<RefreshToken>(entry => {
entry.HasKey(e => e.Id);
entry.Property(e => e.UserId);
entry.Property(e => e.ExpirationDate);
});
modelBuilder.Entity<AccessToken>(entry => {
entry.HasKey(e => e.Id);
entry.Property(e => e.RefreshTokenId);
entry.Property(e => e.ExpirationDate);
});
modelBuilder.Entity<Permission>(entry => {
entry.HasKey(e => e.Id);
entry.Property(e => e.Id).ValueGeneratedOnAdd();
entry.Property(e => e.UserId);
entry.Property(e => e.PermissionName);
entry.Property(e => e.Type);
});
modelBuilder.Entity<FileShare>(entry => {
entry.HasKey(e => e.Id);
entry.Property(e => e.Owner);
entry.Property(e => e.File);
});
}
}
}

View File

@@ -0,0 +1,19 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 4041
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["WebDesktopBackend.csproj", "./"]
RUN dotnet restore "WebDesktopBackend.csproj"
COPY . .
WORKDIR "/src/"
RUN dotnet build "WebDesktopBackend.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "WebDesktopBackend.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebDesktopBackend.dll"]

View File

@@ -0,0 +1,6 @@
namespace WebDesktopBackend.Entitys.Files {
public class DirectoryContent {
public string[] Files { get; set; }
public string[] Directories { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace WebDesktopBackend.Entitys.Files {
public class DirectoryInformation {
public string Name { get; set; }
public DateTime Created { get; set; }
public long Size { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace WebDesktopBackend.Entitys.Files {
public class FileInformation {
public string Name { get; set; }
public DateTime Created { get; set; }
public long Size { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace WebDesktopBackend.Entitys.Files {
public class FileShare {
public string Id { get; set; }
public string Owner { get; set; }
public string File { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace WebDesktopBackend.Entitys.Permissions {
public class Permission {
public const int NotSet = 0;
public const int Allow = 1;
public const int Deny = 2;
public int Id { get; set; }
public string UserId { get; set; }
public string PermissionName { get; set; }
public int Type { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace WebDesktopBackend.Entitys.Permissions {
public class PermissionGroup {
public string Permission { get; set; }
public string Name { get; set; }
public string[] Permissions { get; set; }
public string[] Inherits { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace WebDesktopBackend.Entitys.Tokens {
public class AccessToken {
public string Id { get; set; }
public string RefreshTokenId { get; set; }
public DateTime ExpirationDate { get; set; }
}
public class AccessTokenResponse {
public string Id { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace WebDesktopBackend.Entitys.Tokens {
public class RefreshToken {
public string Id { get; set; }
public string UserId { get; set; }
public DateTime ExpirationDate { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace WebDesktopBackend.Entitys.Tokens {
public class Tokens {
public RefreshToken refreshToken { get; set; }
public AccessToken accessToken { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace WebDesktopBackend.Entitys.User {
public class User : UserEditor {
public string Id { get; set; }
public DateTime Created { get; set; }
public User CreateCopy() {
return new User {
Id = Id,
Created = Created,
FirstName = FirstName,
LastName = LastName,
Email = Email,
Username = Username,
Password = "ENCRYPTED"
};
}
}
}

View File

@@ -0,0 +1,26 @@
namespace WebDesktopBackend.Entitys.User {
public class UserEditor {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public void EditUser(User user) {
Trim();
user.FirstName = string.IsNullOrEmpty(FirstName) ? user.FirstName : FirstName;
user.LastName = string.IsNullOrEmpty(LastName) ? user.LastName : LastName;
user.Email = string.IsNullOrEmpty(Email) ? user.Email : Email;
user.Username = string.IsNullOrEmpty(Username) ? user.Username : Username;
user.Password = string.IsNullOrEmpty(Password) ? user.Password : Password;
}
public void Trim() {
FirstName = FirstName.Trim();
LastName = LastName.Trim();
Email = Email.Trim();
Username = Username.Trim();
Password = Password.Trim();
}
}
}

View File

@@ -0,0 +1,7 @@
namespace WebDesktopBackend.Entitys.User {
public class UserLogin {
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using System.IO;
namespace WebDesktopBackend.Extentions {
public static class DirectoryInfoExtentions {
/// <summary>
/// Returns the size of the Directory (from: https://stackoverflow.com/a/32364847)
/// </summary>
/// <param name="directoryInfo">The Directory</param>
/// <param name="recursive">When set to true, the Functions includes also Subdirectories</param>
/// <returns>The size of the Directory</returns>
public static long GetDirectorySize(this DirectoryInfo directoryInfo, bool recursive = true) {
var startDirectorySize = default(long);
if (directoryInfo == null || !directoryInfo.Exists)
return startDirectorySize; //Return 0 while Directory does not exist.
//Add size of files in the Current Directory to main size.
foreach (var fileInfo in directoryInfo.GetFiles())
System.Threading.Interlocked.Add(ref startDirectorySize, fileInfo.Length);
if (recursive) //Loop on Sub Direcotries in the Current Directory and Calculate it's files size.
System.Threading.Tasks.Parallel.ForEach(directoryInfo.GetDirectories(), (subDirectory) =>
System.Threading.Interlocked.Add(ref startDirectorySize, GetDirectorySize(subDirectory, recursive)));
return startDirectorySize; //Return full Size of this Directory.
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WebDesktopBackend.Extentions {
public static class WebSocketExtentions {
public static async Task SendMessage(this WebSocket socket, string message, Encoding encoding = null) {
encoding ??= Encoding.Default;
await socket.SendAsync(new ArraySegment<byte>(encoding.GetBytes(message)), WebSocketMessageType.Text, true, CancellationToken.None);
}
public static async Task<string> RecieveMessage(this WebSocket socket, Encoding encoding = null) {
encoding ??= Encoding.Default;
byte[] buffer = new byte[1024 * 4];
var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
return encoding.GetString(new ArraySegment<byte>(buffer, 0, result.Count));
}
public static async Task<WebSocket> ConnectAsync(this ClientWebSocket socket, Uri endpoint) {
await socket.ConnectAsync(endpoint, CancellationToken.None);
return socket;
}
public static CancellationTokenSource AddMessageEventHandler(this WebSocket socket, Action<string> handler) {
var source = new CancellationTokenSource();
Task.Run(async () => {
while (!socket.CloseStatus.HasValue) {
string msg = await socket.RecieveMessage();
handler.Invoke(msg);
}
}, source.Token);
return source;
}
}
}

View File

@@ -0,0 +1,144 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using WebDesktopBackend.Contract.Logic;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Files;
using WebDesktopBackend.Extentions;
using WebDesktopBackend.LogicResults;
using WebDesktopBackend.Options;
using WebDesktopBackend.Security;
using FileShare = WebDesktopBackend.Entitys.Files.FileShare;
namespace WebDesktopBackend.Logic {
public class FileLogic : IFileLogic {
private readonly IFileRepository _fileRepository;
private readonly ITokenContext _context;
private readonly FileSystemOptions _options;
public FileLogic(IFileRepository fileRepository, ITokenContext context, IOptions<FileSystemOptions> options) {
_fileRepository = fileRepository;
_context = context;
_options = options.Value;
}
public ILogicResult CreateDirectory(string directory, string name) {
bool success = _fileRepository.CreateDirectory(_options.RootDirectory + _context.UserId + Clean(directory), Clean(name));
if (success) return LogicResult.Ok();
return LogicResult.Conflict();
}
public async Task<ILogicResult> UploadFile(IFormCollection data) {
IFormFile file = data.Files[0];
if (!CheckUserDirectorySize(file.Length)) return LogicResult.Forbidden("Max Directory size reached");
string dir = _options.RootDirectory + _context.UserId + Clean(data["directory"]);
await _fileRepository.UploadFile(file, dir);
return LogicResult.Ok();
}
public async Task<ILogicResult> UploadJson(string directory, string name, string content) {
await _fileRepository.UploadJson(_options.RootDirectory + _context.UserId + Clean(directory), Clean(name) + ".json", content);
return LogicResult.Ok();
}
public ILogicResult<FileStream> DownloadFile(string directory, string file) {
string path = _options.RootDirectory + _context.UserId + Clean(directory) + "/" + Clean(file);
if (!new FileInfo(path).Exists)
return LogicResult<FileStream>.NotFound();
return LogicResult<FileStream>.Ok(_fileRepository.DownloadFile(path));
}
public async Task<ILogicResult<string>> DownloadJson(string file) {
string path = _options.RootDirectory + _context.UserId + Clean(file);
if (!new FileInfo(path).Exists)
return LogicResult<string>.NotFound();
return LogicResult<string>.Ok(await _fileRepository.DownloadJson(path));
}
public ILogicResult<DirectoryContent> GetDirectory(string directory) {
string path = _options.RootDirectory + _context.UserId + Clean(directory);
if (!new DirectoryInfo(path).Exists)
return LogicResult<DirectoryContent>.NotFound();
return LogicResult<DirectoryContent>.Ok(_fileRepository.GetDirectory(path));
}
public ILogicResult<DirectoryInformation> GetDirectoryInformation(string directory) {
string path = _options.RootDirectory + _context.UserId + Clean(directory);
if (!new DirectoryInfo(path).Exists)
return LogicResult<DirectoryInformation>.NotFound();
return LogicResult<DirectoryInformation>.Ok(_fileRepository.GetDirectoryInformation(path));
}
public ILogicResult<FileInformation> GetFileInformation(string directory, string file) {
string path = _options.RootDirectory + _context.UserId + Clean(directory) + "/" + Clean(file);
if (!new FileInfo(path).Exists)
return LogicResult<FileInformation>.NotFound();
return LogicResult<FileInformation>.Ok(_fileRepository.GetFileInformation(path));
}
public ILogicResult MoveDirectory(string directory, string name, string to) {
string path = _options.RootDirectory + _context.UserId + Clean(directory) + Clean(name);
if (!new DirectoryInfo(path).Exists)
return LogicResult.NotFound();
to = _options.RootDirectory + _context.UserId + to;
if (!new DirectoryInfo(to).Exists)
return LogicResult.NotFound();
_fileRepository.MoveDirectory(path, to + "/" + Clean(name));
return LogicResult.Ok();
}
public ILogicResult MoveFile(string directory, string file, string to) {
string path = _options.RootDirectory + _context.UserId + Clean(directory) + Clean(file);
if (!new FileInfo(path).Exists)
return LogicResult.NotFound();
to = _options.RootDirectory + _context.UserId + to;
if (!new DirectoryInfo(to).Exists)
return LogicResult.NotFound();
_fileRepository.MoveFile(path, to + "/" + Clean(file));
return LogicResult.Ok();
}
public ILogicResult Delete(string url) {
_fileRepository.Delete(_options.RootDirectory + _context.UserId + Clean(url));
return LogicResult.Ok();
}
public ILogicResult<FileShare> Share(string url) {
string share = _fileRepository.GenerateShareId(Clean(url), _context.UserId);
if (share != null) {
var result = new FileShare() {
Id = share,
Owner = _context.UserId,
File = url
};
return LogicResult<FileShare>.Ok(result);
}
return LogicResult<FileShare>.Conflict();
}
private bool CheckUserDirectorySize(long fileSize = 0) {
DirectoryInfo info = new DirectoryInfo(_options.RootDirectory + _context.UserId);
if (!info.Exists) return true;
if (info.GetDirectorySize() > _options.MaxSizePerUserInMb * 1000000 - fileSize) return false;
return true;
}
private string Clean(in string input) {
return input.Replace("../", "/").Replace("./", "/");
}
}
}

View File

@@ -0,0 +1,166 @@
using System.Linq;
using WebDesktopBackend.Contract.Logic;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Tokens;
using WebDesktopBackend.Entitys.User;
using WebDesktopBackend.LogicResults;
using WebDesktopBackend.Security;
namespace WebDesktopBackend.Logic {
public class UserLogic : IUserLogic {
private readonly IUserRepository _users;
private readonly ITokenRepository _tokens;
private readonly IGroupRepository _groups;
private readonly IFileRepository _files;
private readonly ITokenContext _context;
public UserLogic(IUserRepository users, ITokenRepository tokens, ITokenContext context, IGroupRepository groups, IFileRepository files) {
_users = users;
_tokens = tokens;
_context = context;
_groups = groups;
_files = files;
}
public ILogicResult<Tokens> Login(UserLogin login) {
if (!_users.Login(login)) return LogicResult<Tokens>.Conflict();
User user = _users.GetUserFromLogin(login);
_tokens.DeleteUserTokens(_context.UserId);
RefreshToken refreshToken = _tokens.CreateRefreshToken(user.Id);
AccessToken accessToken = _tokens.CreateAccessToken(refreshToken.Id);
return LogicResult<Tokens>.Ok(new Tokens {refreshToken = refreshToken, accessToken = accessToken});
}
public ILogicResult<Tokens> Register(UserEditor editor) {
editor.Trim();
if (!ValidateUserdata(editor)) return LogicResult<Tokens>.BadRequest();
User user = _users.AddUser(editor);
_files.InitUser(user.Id);
RefreshToken refreshToken = _tokens.CreateRefreshToken(user.Id);
AccessToken accessToken = _tokens.CreateAccessToken(refreshToken.Id);
return LogicResult<Tokens>.Ok(new Tokens {refreshToken = refreshToken, accessToken = accessToken});
}
public ILogicResult Logout() {
_tokens.DeleteRefreshToken(_context.RefreshTokenId);
return LogicResult.Ok();
}
public ILogicResult EditUser(string id, UserEditor editor) {
editor.Trim();
if (!ValidateEdit(editor)) return LogicResult.BadRequest();
if (_users.GetUser(id) == null) return LogicResult.NotFound();
_users.EditUser(id, editor);
return LogicResult.Ok();
}
public ILogicResult DeleteUser(string id) {
_tokens.DeleteUserTokens(id);
_users.DeleteUser(id);
_files.DeleteUserFolder(id);
return LogicResult.Ok();
}
public ILogicResult<User> GetUser(string id) {
User user = _users.GetUser(id);
if (user == null) return LogicResult<User>.NotFound();
return LogicResult<User>.Ok(user.CreateCopy());
}
public ILogicResult<User[]> GetUsers() {
User[] users = _users.GetUsers();
User[] exports = new User[users.Length];
for (var i = 0; i < users.Length; i++) {
exports[i] = users[i].CreateCopy();
}
return LogicResult<User[]>.Ok(exports);
}
public ILogicResult Valdiate() {
if (string.IsNullOrEmpty(_context.RefreshTokenId) || string.IsNullOrEmpty(_context.AccessTokenId)) return LogicResult.Forbidden();
if (!_tokens.ValidateRefreshToken(_context.RefreshTokenId)) {
_tokens.DeleteRefreshToken(_context.RefreshTokenId);
return LogicResult.Forbidden();
}
return _tokens.ValidateAccessToken(_context.AccessTokenId) ? LogicResult.Ok() : LogicResult.Forbidden();
}
public ILogicResult<AccessTokenResponse> GetToken(string refreshTokenId) {
if (refreshTokenId == null) return LogicResult<AccessTokenResponse>.Forbidden();
if (!_tokens.ValidateRefreshToken(refreshTokenId)) {
_tokens.DeleteRefreshToken(refreshTokenId);
return LogicResult<AccessTokenResponse>.Forbidden();
}
return LogicResult<AccessTokenResponse>.Ok(new AccessTokenResponse {Id = _tokens.CreateAccessToken(refreshTokenId).Id});
}
public ILogicResult<User> GetOwnUser() {
return LogicResult<User>.Ok(_users.GetUser(_context.UserId).CreateCopy());
}
public ILogicResult<string[]> GetPermissions(string id) {
return LogicResult<string[]>.Ok(_groups.GetUserPermissions(id).Select(perm => perm.PermissionName).ToArray());
}
public ILogicResult<string[]> GetRawPermissions(string id) {
return LogicResult<string[]>.Ok(_tokens.GetUserPermissions(id).Select(perm => perm.PermissionName).ToArray());
}
public ILogicResult AddPermission(string id, string permission) {
_tokens.AddPermission(id, permission);
return LogicResult.Ok();
}
public ILogicResult DeletePermission(string id, string permission) {
_tokens.DeletePermission(id, permission);
return LogicResult.Ok();
}
private bool ValidateUserdata(UserEditor editor) {
if (string.IsNullOrEmpty(editor.FirstName)) return false;
if (string.IsNullOrEmpty(editor.LastName)) return false;
if (string.IsNullOrEmpty(editor.Email)) return false;
if (string.IsNullOrEmpty(editor.Username)) return false;
if (string.IsNullOrEmpty(editor.Password)) return false;
if (editor.FirstName.Length > 255) return false;
if (editor.LastName.Length > 255) return false;
if (editor.Email.Length > 255) return false;
if (editor.Username.Length > 255) return false;
if (editor.Password.Length > 255) return false;
if (!editor.Email.Contains('@') || !editor.Email.Contains('.')) return false;
if (editor.Username.Contains('@')) return false;
if (editor.Password.Length < 8) return false;
if (_users.GetUserByUsername(editor.Username) != null) return false;
if (_users.GetUserByEmail(editor.Email) != null) return false;
return true;
}
private bool ValidateEdit(UserEditor editor) {
if (editor.FirstName.Length > 255) return false;
if (editor.LastName.Length > 255) return false;
if (editor.Email.Length > 255) return false;
if (editor.Username.Length > 255) return false;
if (editor.Password.Length > 255) return false;
if (!string.IsNullOrEmpty(editor.Email)) {
if (!editor.Email.Contains('@') || !editor.Email.Contains('.')) return false;
if (_users.GetUserByEmail(editor.Email) != null) return false;
}
if (!string.IsNullOrEmpty(editor.Username)) {
if (editor.Username.Contains('@')) return false;
if (_users.GetUserByUsername(editor.Username) != null) return false;
}
if (!string.IsNullOrEmpty(editor.Password)) {
if (editor.Password.Length < 8) return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Mvc;
namespace WebDesktopBackend.LogicResults {
public static class ControllerBaseExtention {
public static ActionResult FromLogicResult(this ControllerBase controller, ILogicResult result)
{
switch (result.State)
{
case LogicResultState.Ok:
return controller.Ok();
case LogicResultState.BadRequest:
return controller.StatusCode((int)HttpStatusCode.BadRequest, result.Message);
case LogicResultState.Forbidden:
return controller.StatusCode((int)HttpStatusCode.Forbidden, result.Message);
case LogicResultState.NotFound:
return controller.StatusCode((int)HttpStatusCode.NotFound, result.Message);
case LogicResultState.Conflict:
return controller.StatusCode((int)HttpStatusCode.Conflict, result.Message);
default:
throw new Exception("An unhandled result has occurred as a result of a service call.");
}
}
public static ActionResult FromLogicResult<T>(this ControllerBase controller, ILogicResult<T> result)
{
switch (result.State)
{
case LogicResultState.Ok:
return controller.Ok(result.Data);
case LogicResultState.BadRequest:
return controller.StatusCode((int)HttpStatusCode.BadRequest, result.Message);
case LogicResultState.Forbidden:
return controller.StatusCode((int)HttpStatusCode.Forbidden, result.Message);
case LogicResultState.NotFound:
return controller.StatusCode((int)HttpStatusCode.NotFound, result.Message);
case LogicResultState.Conflict:
return controller.StatusCode((int)HttpStatusCode.Conflict, result.Message);
default:
throw new Exception("An unhandled result has occurred as a result of a service call.");
}
}
}
}

View File

@@ -0,0 +1,21 @@
namespace WebDesktopBackend.LogicResults {
public interface ILogicResult
{
LogicResultState State { get; set; }
string Message { get; set; }
bool IsSuccessful { get; }
}
public interface ILogicResult<T>
{
LogicResultState State { get; set; }
T Data { get; set; }
string Message { get; set; }
bool IsSuccessful { get; }
}
}

View File

@@ -0,0 +1,230 @@
namespace WebDesktopBackend.LogicResults {
internal class LogicResult : ILogicResult
{
public LogicResultState State { get; set; }
public string Message { get; set; }
public bool IsSuccessful
{
get
{
return this.State == LogicResultState.Ok;
}
}
public static LogicResult Ok()
{
return new LogicResult()
{
State = LogicResultState.Ok
};
}
public static LogicResult BadRequest()
{
return new LogicResult()
{
State = LogicResultState.BadRequest
};
}
public static LogicResult BadRequest(string message)
{
return new LogicResult()
{
State = LogicResultState.BadRequest,
Message = message
};
}
public static LogicResult Forbidden()
{
return new LogicResult()
{
State = LogicResultState.Forbidden
};
}
public static LogicResult Forbidden(string message)
{
return new LogicResult()
{
State = LogicResultState.Forbidden,
Message = message
};
}
public static LogicResult NotFound()
{
return new LogicResult()
{
State = LogicResultState.NotFound
};
}
public static LogicResult NotFound(string message)
{
return new LogicResult()
{
State = LogicResultState.NotFound,
Message = message
};
}
public static LogicResult Conflict()
{
return new LogicResult()
{
State = LogicResultState.Conflict
};
}
public static LogicResult Conflict(string message)
{
return new LogicResult()
{
State = LogicResultState.Conflict,
Message = message
};
}
public static LogicResult Forward(LogicResult result)
{
return new LogicResult()
{
State = result.State,
Message = result.Message
};
}
public static LogicResult Forward<T>(ILogicResult<T> result)
{
return new LogicResult()
{
State = result.State,
Message = result.Message
};
}
}
internal class LogicResult<T> : ILogicResult<T>
{
public LogicResultState State { get; set; }
public T Data { get; set; }
public string Message { get; set; }
public bool IsSuccessful
{
get
{
return this.State == LogicResultState.Ok;
}
}
public static LogicResult<T> Ok()
{
return new LogicResult<T>()
{
State = LogicResultState.Ok
};
}
public static LogicResult<T> Ok(T result)
{
return new LogicResult<T>()
{
State = LogicResultState.Ok,
Data = result
};
}
public static LogicResult<T> BadRequest()
{
return new LogicResult<T>()
{
State = LogicResultState.BadRequest
};
}
public static LogicResult<T> BadRequest(string message)
{
return new LogicResult<T>()
{
State = LogicResultState.BadRequest,
Message = message
};
}
public static LogicResult<T> Forbidden()
{
return new LogicResult<T>()
{
State = LogicResultState.Forbidden
};
}
public static LogicResult<T> Forbidden(string message)
{
return new LogicResult<T>()
{
State = LogicResultState.Forbidden,
Message = message
};
}
public static LogicResult<T> NotFound()
{
return new LogicResult<T>()
{
State = LogicResultState.NotFound
};
}
public static LogicResult<T> NotFound(string message)
{
return new LogicResult<T>()
{
State = LogicResultState.NotFound,
Message = message
};
}
public static LogicResult<T> Conflict()
{
return new LogicResult<T>()
{
State = LogicResultState.Conflict
};
}
public static LogicResult<T> Conflict(string message)
{
return new LogicResult<T>()
{
State = LogicResultState.Conflict,
Message = message
};
}
public static LogicResult<T> Forward(ILogicResult result)
{
return new LogicResult<T>()
{
State = result.State,
Message = result.Message
};
}
public static LogicResult<T> Forward<T2>(ILogicResult<T2> result)
{
return new LogicResult<T>()
{
State = result.State,
Message = result.Message
};
}
}
}

View File

@@ -0,0 +1,9 @@
namespace WebDesktopBackend.LogicResults {
public enum LogicResultState {
Ok,
BadRequest,
Forbidden,
NotFound,
Conflict
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using MySql.Data.MySqlClient;
namespace WebDesktopBackend {
public class MySql : IDisposable {
public const string ConnectionString = "SERVER=213.136.89.237;DATABASE=WebDesktop;UID=WebDesktop;PASSWORD=Hft6bP@V3IkYvqS1";
private readonly MySqlConnection _connection;
private readonly List<string> _querys;
public MySql() {
_querys = new List<string>();
_connection = new MySqlConnection(ConnectionString);
_connection.Open();
}
public void Insert(string qry) {
if (!qry.EndsWith(";")) qry += ";";
_querys.Add(qry);
}
public void Dispose() {
MySqlCommand cmd = new MySqlCommand(string.Join(" ", _querys), _connection);
cmd.ExecuteNonQuery();
cmd.Dispose();
_connection?.Dispose();
}
}
}

View File

@@ -0,0 +1,8 @@
namespace WebDesktopBackend.Options {
public class FileSystemOptions : OptionsFromConfiguration {
public override string Position => "FileSystem";
public string RootDirectory { get; set; }
public int MaxSizePerUserInMb { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace WebDesktopBackend.Options
{
public abstract class OptionsFromConfiguration
{
public abstract string Position { get; }
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace WebDesktopBackend.Options
{
public static class OptionsFromConfigurationExtensions
{
public static T AddOptionsFromConfiguration<T>(this IServiceCollection services, IConfiguration configuration)
where T : OptionsFromConfiguration
{
T optionsInstance = (T)Activator.CreateInstance(typeof(T));
if (optionsInstance == null) return null;
string position = optionsInstance.Position;
services.Configure((Action<T>)(options =>
{
IConfigurationSection section = configuration.GetSection(position);
if (section != null)
{
section.Bind(options);
}
}));
return optionsInstance;
}
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Files;
using WebDesktopBackend.Extentions;
using WebDesktopBackend.Options;
using FileShare = WebDesktopBackend.Entitys.Files.FileShare;
namespace WebDesktopBackend.Persistance {
public class FileRepository : IFileRepository {
private readonly FileSystemOptions _options;
private readonly DatabaseContext _context;
public FileRepository(IOptions<FileSystemOptions> options, DatabaseContext context) {
_options = options.Value;
_context = context;
}
public void InitUser(string userId) {
CreateDirectory(_options.RootDirectory, userId);
}
public void DeleteUserFolder(string userId) {
Delete(_options.RootDirectory + userId);
}
public bool CreateDirectory(string directory, string name) {
DirectoryInfo info = new DirectoryInfo(directory + "/" + name);
if (info.Exists) return false;
info.Create();
return true;
}
public async Task UploadFile(IFormFile file, string directory) {
DirectoryInfo dir = new DirectoryInfo(directory);
if (!dir.Exists)
dir.Create();
FileInfo fileInfo = new FileInfo(dir + "/" + file.FileName);
if (fileInfo.Exists)
fileInfo.Delete();
FileStream stream = fileInfo.OpenWrite();
await file.CopyToAsync(stream);
stream.Close();
}
public async Task UploadJson(string directory, string name, string data) {
DirectoryInfo dir = new DirectoryInfo(directory);
if (!dir.Exists)
dir.Create();
FileInfo file = new FileInfo(directory + "/" + name);
if (file.Exists)
file.Delete();
byte[] bytes = Encoding.UTF8.GetBytes(data);
FileStream stream = file.Create();
await stream.WriteAsync(bytes, 0, bytes.Length);
stream.Close();
}
public FileStream DownloadFile(string path) {
FileInfo file = new FileInfo(path);
return file.OpenRead();
}
public Task<string> DownloadJson(string file) {
return File.ReadAllTextAsync(file);
}
public DirectoryContent GetDirectory(string directory) {
DirectoryInfo dir = new DirectoryInfo(directory);
return new DirectoryContent() {
Files = dir.GetFiles().Select(file => file.Name).ToArray(),
Directories = dir.GetDirectories().Select(info => info.Name).ToArray()
};
}
public DirectoryInformation GetDirectoryInformation(string directory) {
DirectoryInfo info = new DirectoryInfo(directory);
return new DirectoryInformation {
Name = info.Name,
Created = Directory.GetCreationTime(directory),
Size = info.GetDirectorySize()
};
}
public FileInformation GetFileInformation(string file) {
FileInfo info = new FileInfo(file);
return new FileInformation() {
Name = info.Name,
Created = File.GetCreationTime(file),
Size = info.Length
};
}
public void MoveDirectory(string directory, string to) {
DirectoryInfo info = new DirectoryInfo(directory);
info.MoveTo(to);
}
public void MoveFile(string file, string to) {
FileInfo info = new FileInfo(file);
info.MoveTo(to);
}
public void Delete(string url) {
if (File.Exists(url))
File.Delete(url);
if (Directory.Exists(url))
Directory.Delete(url, true);
}
public string GenerateShareId(string url, string owner) {
FileShare share = new FileShare();
share.File = url;
share.Owner = owner;
share.Id = Guid.NewGuid().ToString();
_context.FileShares.Add(share);
_context.SaveChanges();
return share.Id;
}
}
}

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Linq;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Permissions;
namespace WebDesktopBackend.Persistance {
public class GroupRepository : IGroupRepository {
private readonly ITokenRepository _tokens;
private readonly PermissionGroup[] _groups;
public GroupRepository(ITokenRepository tokens) {
_tokens = tokens;
_groups = Program.Groups;
}
public PermissionGroup GetPermissionGroup(string name) {
return _groups.SingleOrDefault(group => group.Permission.Equals(name));
}
public PermissionGroup[] GetGroupsFromUser(string userId) {
Permission[] permissions = _tokens.GetUserPermissions(userId);
return ExtractGroups(permissions);
}
public PermissionGroup[] ExtractGroups(Permission[] permissions) {
List<PermissionGroup> permissionGroups = new List<PermissionGroup>();
foreach (var permission in permissions) {
if (permission.PermissionName.StartsWith("group.")) {
foreach (var permissionGroup in _groups) {
if (permission.PermissionName.Equals(permissionGroup.Permission)) {
permissionGroups.Add(permissionGroup);
if (permissionGroup.Inherits is not null) {
foreach (var inherit in permissionGroup.Inherits) {
permissionGroups.Add(GetPermissionGroup(inherit));
}
}
}
}
}
}
return permissionGroups.ToArray();
}
public Permission[] GetUserPermissions(string id) {
List<Permission> permissions = _tokens.GetUserPermissions(id)
.Where(perm => perm.Type == Permission.Allow).ToList();
PermissionGroup[] groups = ExtractGroups(permissions.ToArray());
foreach (var group in groups) {
if (group.Permissions is null) continue;
permissions.AddRange(group.Permissions
.Select(perm => new Permission {Id = -1, UserId = id, Type = Permission.Allow, PermissionName = perm}));
}
return permissions.ToArray();
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Permissions;
using WebDesktopBackend.Entitys.Tokens;
using WebDesktopBackend.Security.Authentication;
namespace WebDesktopBackend.Persistance {
public class TokenRepository : ITokenRepository {
private readonly JwtTokenAuthenticationOptions _options;
private readonly DatabaseContext _context;
public TokenRepository(IOptions<JwtTokenAuthenticationOptions> options, DatabaseContext context) {
_options = options.Value;
_context = context;
}
public RefreshToken GetRefreshToken(string id) {
if (string.IsNullOrEmpty(id)) return null;
return _context.RefreshTokens.Where(token => token.Id == id).SingleOrDefault();
}
public AccessToken GetAccessToken(string id) {
if (string.IsNullOrEmpty(id)) return null;
return _context.AccessTokens.Where(token => token.Id == id).SingleOrDefault();
}
public bool ValidateAccessToken(string id) {
AccessToken token = GetAccessToken(id);
if (token == null) return false;
TimeSpan span = token.ExpirationDate - DateTime.Now;
return span.TotalMilliseconds > 0;
}
public bool ValidateRefreshToken(string id) {
RefreshToken token = GetRefreshToken(id);
if (token == null) return false;
TimeSpan span = token.ExpirationDate - DateTime.Now;
return span.TotalMilliseconds > 0;
}
public RefreshToken CreateRefreshToken(string userId) {
RefreshToken token = new RefreshToken { UserId = userId, Id = Guid.NewGuid().ToString(), ExpirationDate = DateTime.Now.Add(new TimeSpan(int.Parse(_options.RefreshTokenExpirationTimeInHours), 0, 0)) };
_context.RefreshTokens.Add(token);
_context.SaveChanges();
return token;
}
public AccessToken CreateAccessToken(string refreshTokenId) {
AccessToken token = new AccessToken { RefreshTokenId = refreshTokenId, Id = Guid.NewGuid().ToString(), ExpirationDate = DateTime.Now.Add(new TimeSpan(0, int.Parse(_options.AccessTokenExpirationTimeInMinutes), 0)) };
_context.AccessTokens.Add(token);
_context.SaveChanges();
return token;
}
public void DeleteUserTokens(string id) {
List<RefreshToken> refreshTokens = _context.RefreshTokens.Where(token => token.UserId == id).ToList();
refreshTokens.ForEach(token => DeleteRefreshToken(token.Id));
}
public void DeleteRefreshToken(string id) {
_context.RefreshTokens.RemoveRange(_context.RefreshTokens.Where(token => token.Id == id));
_context.AccessTokens.RemoveRange(_context.AccessTokens.Where(token => token.RefreshTokenId == id));
}
public Permission[] GetUserPermissions(string id) {
return _context.Permissions.Where(permission => permission.UserId == id).ToArray();
}
public void AddPermission(string id, string permission) {
_context.Permissions.Add(new Permission
{ PermissionName = permission, UserId = id, Type = Permission.Allow });
_context.SaveChanges();
}
public void DeletePermission(string id, string permission) {
_context.Permissions.Remove(_context.Permissions.Single(perm =>
perm.UserId == id && perm.PermissionName == permission));
_context.SaveChanges();
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using Microsoft.Extensions.Configuration;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Permissions;
using WebDesktopBackend.Entitys.User;
namespace WebDesktopBackend.Persistance {
public class UserRepository : IUserRepository {
private readonly DatabaseContext _context;
private readonly ITokenRepository _tokens;
private readonly IConfiguration _configuration;
public UserRepository(DatabaseContext context, ITokenRepository tokens, IConfiguration configuration) {
_context = context;
_tokens = tokens;
_configuration = configuration;
}
public User AddUser(UserEditor editor) {
User user = new User { Id = Guid.NewGuid().ToString(), Created = DateTime.Now };
editor.EditUser(user);
user.Password = Hash128(user.Password);
_context.Users.Add(user);
_context.Permissions.Add(new Permission()
{ PermissionName = "group.user", UserId = user.Id, Type = Permission.Allow });
_context.SaveChanges();
return user;
}
public void EditUser(string id, UserEditor editor) {
User user = GetUser(id);
if (!string.IsNullOrEmpty(editor.Password))
editor.Password = Hash128(editor.Password);
editor.EditUser(user);
_context.SaveChanges();
}
public void DeleteUser(string id) {
_context.Users.RemoveRange(_context.Users.Where(user => user.Id == id));
_context.Permissions.RemoveRange(_context.Permissions.Where(permission => permission.UserId == id));
_tokens.DeleteUserTokens(id);
_context.SaveChanges();
}
public User GetUser(string id) {
return _context.Users.SingleOrDefault(user => user.Id == id);
}
public User GetUserByUsername(string username) {
return _context.Users.SingleOrDefault(user => user.Username == username);
}
public User GetUserByEmail(string email) {
return _context.Users.SingleOrDefault(user => user.Email == email);
}
public User GetUserFromLogin(UserLogin login) {
if (!string.IsNullOrEmpty(login.Username)) return GetUserByUsername(login.Username);
if (!string.IsNullOrEmpty(login.Email)) return GetUserByEmail(login.Email);
return null;
}
public User[] GetUsers() {
return _context.Users.OrderBy(user => user.Created).ToArray();
}
public bool Login(UserLogin login) {
User user = GetUserFromLogin(login);
if (user == null || string.IsNullOrEmpty(user.Password)) return false;
return user.Password.Equals(Hash128(login.Password));
}
private string Hash128(string plainText) {
try {
byte[] salt = _configuration.GetSection("PasswordSalt").Get<byte[]>();
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: plainText,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 100000,
numBytesRequested: 256 / 8
));
return hashed;
} catch (Exception) { return ""; }
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using WebDesktopBackend.Entitys.Permissions;
namespace WebDesktopBackend {
public class Program {
public static PermissionGroup[] Groups;
public static void Main(string[] args) {
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.Build();
//COMPILE GROUPS
var groupsSections = configuration.GetSection("Groups").GetChildren();
List<PermissionGroup> groups = new List<PermissionGroup>();
foreach (var section in groupsSections) {
PermissionGroup group = new PermissionGroup();
group.Name = section.GetValue<string>("Name");
group.Permission = section.GetValue<string>("Permission");
group.Permissions = section.GetSection("Permissions").Get<string[]>();
group.Inherits = section.GetSection("Inherits").Get<string[]>();
groups.Add(group);
}
Groups = groups.ToArray();
CreateHostBuilder(args, configuration)
.Build()
.Run();
}
private static IHostBuilder CreateHostBuilder(string[] args, IConfiguration configuration) {
var config = configuration.GetSection("WebServer");
return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<Startup>();
webBuilder.UseUrls("http://0.0.0.0:" + config.GetValue<int>("Port"));
});
}
}
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:21799",
"sslPort": 44331
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebDesktopBackend": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace WebDesktopBackend.Security.Authentication
{
public static class JwtTokenAuthentication
{
public const string Scheme = "JwtTokenAuthentication";
}
}

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using WebDesktopBackend.Options;
namespace WebDesktopBackend.Security.Authentication
{
public static class JwtTokenAuthenticationExtensions
{
public static AuthenticationBuilder AddJwtTokenAuthentication(this AuthenticationBuilder builder, IConfiguration configuration)
{
builder.Services.AddOptionsFromConfiguration<JwtTokenAuthenticationOptions>(configuration);
return builder.AddScheme<JwtTokenAuthenticationHandlerOptions, JwtTokenAuthenticationHandler>(
JwtTokenAuthentication.Scheme,
_ => { });
}
}
}

View File

@@ -0,0 +1,82 @@
using Microsoft.AspNetCore.Authentication;
using WebDesktopBackend.Entitys.Tokens;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Entitys.Permissions;
using WebDesktopBackend.Security.Authorization;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace WebDesktopBackend.Security.Authentication
{
public class JwtTokenAuthenticationHandler : AuthenticationHandler<JwtTokenAuthenticationHandlerOptions>
{
private readonly ITokenRepository _tokens;
private readonly IGroupRepository _groups;
public JwtTokenAuthenticationHandler(
IOptionsMonitor<JwtTokenAuthenticationHandlerOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
ITokenRepository tokens,
IGroupRepository groups)
: base(options, logger, encoder, clock)
{
_tokens = tokens;
_groups = groups;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var accessToken = GetAccessToken();
if (accessToken == null) return AuthenticateResult.Fail("Access Token invalid");
var refreshToken = _tokens.GetRefreshToken(accessToken.RefreshTokenId);
if (refreshToken == null) return AuthenticateResult.Fail("Refresh Token invalid");
if (!_tokens.ValidateRefreshToken(refreshToken.Id)) return AuthenticateResult.Fail("Refresh Token invalid");
bool valid = _tokens.ValidateAccessToken(accessToken.Id);
return valid ?
AuthenticateResult.Success(GetAuthenticationTicket(accessToken, refreshToken)) :
AuthenticateResult.Fail("Access Token invalid");
}
private AuthenticationTicket GetAuthenticationTicket(AccessToken accessToken, RefreshToken refreshToken) {
List<Claim> claims = GenerateClaims(accessToken, refreshToken);
ClaimsPrincipal principal = new ClaimsPrincipal();
principal.AddIdentity(new ClaimsIdentity(claims, JwtTokenAuthentication.Scheme));
AuthenticationTicket ticket = new AuthenticationTicket(principal, Scheme.Name);
return ticket;
}
private List<Claim> GenerateClaims(AccessToken accessToken, RefreshToken refreshToken) {
List<Claim> claims = new List<Claim>() {
new (CustomClaimTypes.AccessTokenId, accessToken.Id),
new (CustomClaimTypes.RefreshTokenId, refreshToken.Id),
new (CustomClaimTypes.UserId, refreshToken.UserId),
};
Permission[] permissions = _groups.GetUserPermissions(refreshToken.UserId)
.Where(perm => perm.Type == Permission.Allow).ToArray();
claims.AddRange(permissions.Select(permission => new Claim(CustomClaimTypes.Permission, permission.PermissionName)));
return claims;
}
private AccessToken GetAccessToken() {
string key = Request.Headers["Authorization"];
if (string.IsNullOrEmpty(key)) {
key = Request.Query["token"];
}
AccessToken token = _tokens.GetAccessToken(key);
return token;
}
}
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously

View File

@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authentication;
namespace WebDesktopBackend.Security.Authentication
{
public class JwtTokenAuthenticationHandlerOptions : AuthenticationSchemeOptions
{
// Options for the authentication handler.
// Currently: None
}
}

View File

@@ -0,0 +1,12 @@
using WebDesktopBackend.Options;
namespace WebDesktopBackend.Security.Authentication
{
public class JwtTokenAuthenticationOptions : OptionsFromConfiguration
{
public override string Position => "JwtTokenAuthentication:Jwt";
public string RefreshTokenExpirationTimeInHours { get; set; }
public string AccessTokenExpirationTimeInMinutes { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc;
namespace WebDesktopBackend.Security.Authorization
{
public sealed class AuthorizedAttribute : TypeFilterAttribute
{
public AuthorizedAttribute(params string[] permission) : base(typeof(AuthorizedFilter)) {
Arguments = new object[] { permission };
}
}
}

View File

@@ -0,0 +1,67 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Linq;
namespace WebDesktopBackend.Security.Authorization
{
public class AuthorizedFilter : IAuthorizationFilter
{
private readonly string[] _permissions;
public AuthorizedFilter(params string[] permissions)
{
_permissions = permissions;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (EndpointHasAllowAnonymousFilter(context))
{
return;
}
if (!IsAuthenticated(context))
{
context.Result = new UnauthorizedResult();
return;
}
if (!ContainsRequiredRole(context))
{
context.Result = new ForbidResult();
return;
}
}
private static bool EndpointHasAllowAnonymousFilter(AuthorizationFilterContext context)
{
return context.Filters.Any(item => item is IAllowAnonymousFilter);
}
private bool IsAuthenticated(AuthorizationFilterContext context)
{
return context.HttpContext.User.Identity.IsAuthenticated;
}
private bool ContainsRequiredRole(AuthorizationFilterContext context) {
if (_permissions.Length == 0)
return true;
if (context.HttpContext.User.HasClaim(CustomClaimTypes.Permission, "*"))
return true;
foreach (var permission in _permissions) {
string[] splice = permission.Split(".");
string cache = "";
foreach (var s in splice) {
cache += s + ".";
if (context.HttpContext.User.HasClaim(CustomClaimTypes.Permission, cache + "*"))
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Linq;
using System.Security.Claims;
namespace WebDesktopBackend.Security.Authorization
{
public static class ClaimsPrincipalExtensions
{
public static string GetAccessTokenId(this ClaimsPrincipal principal) => principal.FindFirstValue(CustomClaimTypes.AccessTokenId);
public static string GetRefreshTokenId(this ClaimsPrincipal principal) => principal.FindFirstValue(CustomClaimTypes.RefreshTokenId);
public static string GetUserId(this ClaimsPrincipal principal) => principal.FindFirstValue(CustomClaimTypes.UserId);
public static string[] GetPermissions(this ClaimsPrincipal principal) => principal.Claims
.Where(claim => claim.Type.Equals(CustomClaimTypes.Permission))
.Select(claim => claim.Value)
.ToArray();
}
}

View File

@@ -0,0 +1,10 @@
namespace WebDesktopBackend.Security.Authorization
{
public static class CustomClaimTypes
{
public const string AccessTokenId = "WebDesktop.AccessTokenId";
public const string RefreshTokenId = "WebDesktop.RefreshTokenId";
public const string UserId = "WebDesktop.UserId";
public const string Permission = "WebDesktop.Permission";
}
}

View File

@@ -0,0 +1,10 @@
namespace WebDesktopBackend.Security
{
public interface ITokenContext {
bool IsAuthenticated {get;}
string UserId {get;}
string AccessTokenId {get;}
string RefreshTokenId {get;}
string[] Permissions { get; }
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Http;
using WebDesktopBackend.Security.Authorization;
namespace WebDesktopBackend.Security
{
internal class TokenContext : ITokenContext
{
private readonly IHttpContextAccessor _accessor;
public TokenContext(IHttpContextAccessor accessor) {
_accessor = accessor;
}
public bool IsAuthenticated => _accessor.HttpContext.User.Identity.IsAuthenticated;
public string UserId => _accessor.HttpContext?.User.GetUserId();
public string AccessTokenId => _accessor.HttpContext?.User.GetAccessTokenId();
public string RefreshTokenId => _accessor.HttpContext?.User.GetRefreshTokenId();
public string[] Permissions => _accessor.HttpContext?.User.GetPermissions();
}
}

View File

@@ -0,0 +1,95 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using WebDesktopBackend.Contract.Logic;
using WebDesktopBackend.Contract.Persistance;
using WebDesktopBackend.Controller;
using WebDesktopBackend.Logic;
using WebDesktopBackend.Options;
using WebDesktopBackend.Persistance;
using WebDesktopBackend.Security;
using WebDesktopBackend.Security.Authentication;
namespace WebDesktopBackend {
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
CreateTables();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<DatabaseContext>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<ITokenContext, TokenContext>();
//Repositorys
services.AddScoped<ITokenRepository, TokenRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IFileRepository, FileRepository>();
services.AddScoped<IGroupRepository, GroupRepository>();
//Logic
services.AddScoped<IUserLogic, UserLogic>();
services.AddScoped<IFileLogic, FileLogic>();
services.AddOptionsFromConfiguration<JwtTokenAuthenticationOptions>(Configuration);
services.AddOptionsFromConfiguration<FileSystemOptions>(Configuration);
services.AddCors();
services.AddAuthentication(JwtTokenAuthentication.Scheme).AddJwtTokenAuthentication(Configuration);
services.AddControllers();
services.Configure<FormOptions>(x => {
x.KeyLengthLimit = int.MaxValue;
x.ValueCountLimit = int.MaxValue;
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = long.MaxValue;
x.BufferBodyLengthLimit = long.MaxValue;
x.MultipartBoundaryLengthLimit = int.MaxValue;
x.MultipartHeadersCountLimit = int.MaxValue;
x.MultipartHeadersLengthLimit = int.MaxValue;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsProduction()) {
app.UseHttpsRedirection();
}
app.UseCors(
options => options
.WithOrigins(Configuration.GetSection("WebServer:Origins").Get<string[]>())
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseWebSockets();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
private void CreateTables() {
using var mySql = new MySql();
mySql.Insert("CREATE TABLE IF NOT EXISTS Users (Id VARCHAR(50) PRIMARY KEY, FirstName VARCHAR(255), LastName VARCHAR(255), Email VARCHAR(255), Username VARCHAR(255), Password VARCHAR(255), Created TIMESTAMP)");
mySql.Insert("CREATE TABLE IF NOT EXISTS RefreshTokens (Id VARCHAR(50) PRIMARY KEY, UserId VARCHAR(50), ExpirationDate TIMESTAMP)");
mySql.Insert("CREATE TABLE IF NOT EXISTS AccessTokens (Id VARCHAR(50) PRIMARY KEY, RefreshTokenId VARCHAR(50), ExpirationDate TIMESTAMP)");
mySql.Insert("CREATE TABLE IF NOT EXISTS Permissions (Id INT PRIMARY KEY AUTO_INCREMENT, UserId VARCHAR(50), PermissionName VARCHAR(100), Type INT(1))");
mySql.Insert("CREATE TABLE IF NOT EXISTS FileShares (Id VARCHAR(50) PRIMARY KEY, Owner VARCHAR(50), Files TEXT)");
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="idunno.Authentication.Certificate" Version="2.2.3" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
{
"BlenderRenderer": {
"BlenderPath": "C:\\Program Files\\Blender Foundation\\Blender 3.0\\",
"BlenderVersion": "3.0"
},
"FileSystem": {
"RootDirectory": "./Uploads/"
}
}

View File

@@ -0,0 +1,46 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"WebDesktopBackend.Security.Authentication.JwtTokenAuthenticationHandler": "None"
}
},
"AllowedHosts": "*",
"JwtTokenAuthentication": {
"Jwt": {
"RefreshTokenExpirationTimeInHours": 12,
"AccessTokenExpirationTimeInMinutes": 5
}
},
"PasswordSalt": [237,209,57,94,32,3,146,144,139,153,6,50,215,113,235,250],
"WebServer": {
"Origins": ["https://leon-hoppe.de", "http://localhost:8080", "http://localhost:4200"],
"Port": 4041
},
"Groups": [
{
"Permission": "group.admin",
"Name": "Admin",
"Inherits": [],
"Permissions": ["*"]
},
{
"Permission": "group.moderator",
"Name": "Moderator",
"Inherits": [],
"Permissions": ["app.*"]
},
{
"Permission": "group.user",
"Name": "User",
"Inherits": [],
"Permissions": []
}
],
"FileSystem": {
"RootDirectory": "/home/files/",
"MaxSizePerUserInMb": 1024
}
}

View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "6.0",
"rollForward": "latestMajor",
"allowPrerelease": false
}
}

View File

@@ -0,0 +1,16 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

View File

@@ -0,0 +1,9 @@
.git
.firebase
.editorconfig
/node_modules
/e2e
/docs
.gitignore
*.zip
*.md

View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,10 @@
#stage 1
FROM node:latest as node
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build --prod
#stage 2
FROM nginx:alpine
COPY nginx.conf /etc/nginx/sites-available/default
COPY --from=node /app/dist/WebDesktopFrontend /usr/share/nginx/html

View File

@@ -0,0 +1,27 @@
# WebDesktopFrontend
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.0.1.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,244 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"WebDesktopFrontend": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true,
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/WebDesktopFrontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"./node_modules/bootstrap/dist/css/bootstrap.css",
"src/fonts.scss",
"src/variables.scss",
"src/styles.scss",
"src/themes.scss"
],
"scripts": [
"./node_modules/bootstrap/dist/js/bootstrap.bundle.js",
"./node_modules/codemirror/lib/codemirror.js",
"./node_modules/codemirror/addon/mode/simple.js",
"./node_modules/codemirror/mode/apl/apl.js",
"./node_modules/codemirror/mode/asciiarmor/asciiarmor.js",
"./node_modules/codemirror/mode/asn.1/asn.1.js",
"./node_modules/codemirror/mode/asterisk/asterisk.js",
"./node_modules/codemirror/mode/brainfuck/brainfuck.js",
"./node_modules/codemirror/mode/clike/clike.js",
"./node_modules/codemirror/mode/clojure/clojure.js",
"./node_modules/codemirror/mode/cmake/cmake.js",
"./node_modules/codemirror/mode/cobol/cobol.js",
"./node_modules/codemirror/mode/coffeescript/coffeescript.js",
"./node_modules/codemirror/mode/commonlisp/commonlisp.js",
"./node_modules/codemirror/mode/crystal/crystal.js",
"./node_modules/codemirror/mode/css/css.js",
"./node_modules/codemirror/mode/cypher/cypher.js",
"./node_modules/codemirror/mode/d/d.js",
"./node_modules/codemirror/mode/dart/dart.js",
"./node_modules/codemirror/mode/diff/diff.js",
"./node_modules/codemirror/mode/django/django.js",
"./node_modules/codemirror/mode/dockerfile/dockerfile.js",
"./node_modules/codemirror/mode/dtd/dtd.js",
"./node_modules/codemirror/mode/dylan/dylan.js",
"./node_modules/codemirror/mode/ebnf/ebnf.js",
"./node_modules/codemirror/mode/ecl/ecl.js",
"./node_modules/codemirror/mode/eiffel/eiffel.js",
"./node_modules/codemirror/mode/elm/elm.js",
"./node_modules/codemirror/mode/erlang/erlang.js",
"./node_modules/codemirror/mode/factor/factor.js",
"./node_modules/codemirror/mode/fcl/fcl.js",
"./node_modules/codemirror/mode/forth/forth.js",
"./node_modules/codemirror/mode/fortran/fortran.js",
"./node_modules/codemirror/mode/gas/gas.js",
"./node_modules/codemirror/mode/gfm/gfm.js",
"./node_modules/codemirror/mode/gherkin/gherkin.js",
"./node_modules/codemirror/mode/go/go.js",
"./node_modules/codemirror/mode/groovy/groovy.js",
"./node_modules/codemirror/mode/haml/haml.js",
"./node_modules/codemirror/mode/handlebars/handlebars.js",
"./node_modules/codemirror/mode/haskell/haskell.js",
"./node_modules/codemirror/mode/haskell-literate/haskell-literate.js",
"./node_modules/codemirror/mode/haxe/haxe.js",
"./node_modules/codemirror/mode/htmlembedded/htmlembedded.js",
"./node_modules/codemirror/mode/htmlmixed/htmlmixed.js",
"./node_modules/codemirror/mode/http/http.js",
"./node_modules/codemirror/mode/idl/idl.js",
"./node_modules/codemirror/mode/javascript/javascript.js",
"./node_modules/codemirror/mode/jinja2/jinja2.js",
"./node_modules/codemirror/mode/jsx/jsx.js",
"./node_modules/codemirror/mode/julia/julia.js",
"./node_modules/codemirror/mode/livescript/livescript.js",
"./node_modules/codemirror/mode/lua/lua.js",
"./node_modules/codemirror/mode/markdown/markdown.js",
"./node_modules/codemirror/mode/mathematica/mathematica.js",
"./node_modules/codemirror/mode/mbox/mbox.js",
"./node_modules/codemirror/mode/mirc/mirc.js",
"./node_modules/codemirror/mode/mllike/mllike.js",
"./node_modules/codemirror/mode/modelica/modelica.js",
"./node_modules/codemirror/mode/mscgen/mscgen.js",
"./node_modules/codemirror/mode/mumps/mumps.js",
"./node_modules/codemirror/mode/nginx/nginx.js",
"./node_modules/codemirror/mode/nsis/nsis.js",
"./node_modules/codemirror/mode/ntriples/ntriples.js",
"./node_modules/codemirror/mode/octave/octave.js",
"./node_modules/codemirror/mode/oz/oz.js",
"./node_modules/codemirror/mode/pascal/pascal.js",
"./node_modules/codemirror/mode/pegjs/pegjs.js",
"./node_modules/codemirror/mode/perl/perl.js",
"./node_modules/codemirror/mode/php/php.js",
"./node_modules/codemirror/mode/pig/pig.js",
"./node_modules/codemirror/mode/powershell/powershell.js",
"./node_modules/codemirror/mode/properties/properties.js",
"./node_modules/codemirror/mode/protobuf/protobuf.js",
"./node_modules/codemirror/mode/pug/pug.js",
"./node_modules/codemirror/mode/puppet/puppet.js",
"./node_modules/codemirror/mode/python/python.js",
"./node_modules/codemirror/mode/q/q.js",
"./node_modules/codemirror/mode/r/r.js",
"./node_modules/codemirror/mode/rpm/rpm.js",
"./node_modules/codemirror/mode/rst/rst.js",
"./node_modules/codemirror/mode/ruby/ruby.js",
"./node_modules/codemirror/mode/rust/rust.js",
"./node_modules/codemirror/mode/sas/sas.js",
"./node_modules/codemirror/mode/sass/sass.js",
"./node_modules/codemirror/mode/scheme/scheme.js",
"./node_modules/codemirror/mode/shell/shell.js",
"./node_modules/codemirror/mode/sieve/sieve.js",
"./node_modules/codemirror/mode/slim/slim.js",
"./node_modules/codemirror/mode/smalltalk/smalltalk.js",
"./node_modules/codemirror/mode/smarty/smarty.js",
"./node_modules/codemirror/mode/solr/solr.js",
"./node_modules/codemirror/mode/soy/soy.js",
"./node_modules/codemirror/mode/sparql/sparql.js",
"./node_modules/codemirror/mode/spreadsheet/spreadsheet.js",
"./node_modules/codemirror/mode/sql/sql.js",
"./node_modules/codemirror/mode/stex/stex.js",
"./node_modules/codemirror/mode/stylus/stylus.js",
"./node_modules/codemirror/mode/swift/swift.js",
"./node_modules/codemirror/mode/tcl/tcl.js",
"./node_modules/codemirror/mode/textile/textile.js",
"./node_modules/codemirror/mode/tiddlywiki/tiddlywiki.js",
"./node_modules/codemirror/mode/tiki/tiki.js",
"./node_modules/codemirror/mode/toml/toml.js",
"./node_modules/codemirror/mode/tornado/tornado.js",
"./node_modules/codemirror/mode/troff/troff.js",
"./node_modules/codemirror/mode/ttcn/ttcn.js",
"./node_modules/codemirror/mode/ttcn-cfg/ttcn-cfg.js",
"./node_modules/codemirror/mode/turtle/turtle.js",
"./node_modules/codemirror/mode/twig/twig.js",
"./node_modules/codemirror/mode/vb/vb.js",
"./node_modules/codemirror/mode/vbscript/vbscript.js",
"./node_modules/codemirror/mode/velocity/velocity.js",
"./node_modules/codemirror/mode/verilog/verilog.js",
"./node_modules/codemirror/mode/vhdl/vhdl.js",
"./node_modules/codemirror/mode/vue/vue.js",
"./node_modules/codemirror/mode/wast/wast.js",
"./node_modules/codemirror/mode/webidl/webidl.js",
"./node_modules/codemirror/mode/xml/xml.js",
"./node_modules/codemirror/mode/xquery/xquery.js",
"./node_modules/codemirror/mode/yacas/yacas.js",
"./node_modules/codemirror/mode/yaml/yaml.js",
"./node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js",
"./node_modules/codemirror/mode/z80/z80.js"
],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500mb",
"maximumError": "500mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "1024mb",
"maximumError": "1024mb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "WebDesktopFrontend:build:production"
},
"development": {
"browserTarget": "WebDesktopFrontend:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "WebDesktopFrontend:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.css"
],
"scripts": []
}
}
}
}
},
"defaultProject": "WebDesktopFrontend"
}

View File

@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/WebDesktopFrontend'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@@ -0,0 +1,13 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
{
"name": "web-desktop-frontend",
"version": "0.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "~13.0.0",
"@angular/cdk": "^13.0.0",
"@angular/common": "~13.0.0",
"@angular/compiler": "~13.0.0",
"@angular/core": "~13.0.0",
"@angular/forms": "~13.0.0",
"@angular/material": "^13.0.2",
"@angular/platform-browser": "~13.0.0",
"@angular/platform-browser-dynamic": "~13.0.0",
"@angular/router": "~13.0.0",
"@angular/service-worker": "~13.0.0",
"@ctrl/ngx-codemirror": "^5.1.1",
"bootstrap": "^5.1.3",
"codemirror": "^5.65.2",
"file-saver": "^2.0.5",
"npm": "^8.3.0",
"rxjs": "~7.4.0",
"sweetalert2": "^11.4.4",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.0.1",
"@angular/cli": "~13.0.1",
"@angular/compiler-cli": "~13.0.0",
"@types/codemirror": "^5.60.5",
"@types/file-saver": "^2.0.5",
"@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1",
"jasmine-core": "~3.10.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.4.3"
}
}

View File

@@ -0,0 +1,90 @@
import { Injectable } from '@angular/core';
import { CrudService } from "../crud.service";
import {DirectoryContent, DirectoryInformation, FileInformation} from "../entitys/files";
import {Observable} from "rxjs";
import {HttpEvent} from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class FileAPI {
constructor(private service: CrudService) {}
public async createDirectory(directory: string, name: string): Promise<boolean> {
try {
await this.service.sendPostRequest<boolean>("files/upload/directory?directory=" + directory + "&name=" + name, null, {authorized: true});
return true;
}catch {
return false;
}
}
public async uploadFile(file: File, directory: string): Promise<Observable<HttpEvent<object>>> {
const form = new FormData();
form.append("file", file, file.name);
form.append("directory", directory);
await this.service.getNewToken();
return this.service.HttpClient.post(this.service.endpoint + "files/upload/file", form, {headers: this.service.httpHeader, reportProgress: true, observe: "events"});
}
public async quickUpload(file: File, directory: string) {
const form = new FormData();
form.append("file", file, file.name);
form.append("directory", directory);
await this.service.sendPostRequest("files/upload/file", form);
}
public async uploadJson(directory: string, name: string, content: any) {
await this.service.sendPostRequest("files/upload/json?directory=" + directory + "&name=" + name, content, {authorized: true});
}
public async downloadJson<T>(file: string) {
return await this.service.sendGetRequest<T>("files/download/json?file=" + file, {authorized: true});
}
public async downloadFile(directory: string, file: string): Promise<Observable<HttpEvent<object>>> {
await this.service.getNewToken();
return this.service.HttpClient.get(this.service.endpoint + "files/download/file?directory=" + directory + "&file=" + file, {headers: this.service.httpHeader, responseType: 'blob', reportProgress: true, observe: "events"});
}
public async delete(url: string) {
await this.service.sendDeleteRequest("files/delete?url=" + url, {authorized: true});
}
public async getDirectoryContent(directory: string): Promise<DirectoryContent> {
try {
return await this.service.sendGetRequest<DirectoryContent>("files/content?directory=" + directory, {authorized: true});
} catch {
return { files: [], directories: [] };
}
}
public async getDirectoryInfo(directory: string): Promise<DirectoryInformation> {
const info = await this.service.sendGetRequest<DirectoryInformation>("files/info/directory?directory=" + directory, {authorized: true});
info.created = new Date(info.created);
return info;
}
public async getFileInfo(directory: string, file: string): Promise<FileInformation> {
const info = await this.service.sendGetRequest<FileInformation>("files/info/files?directory=" + directory + "&file=" + file, {authorized: true});
info.created = new Date(info.created);
return info;
}
public async moveDirectory(directory: string, name: string, to: string) {
await this.service.sendPutRequest("files/move/directory?directory=" + directory + "&name=" + name + "&to=" + to, null, {authorized: true});
}
public async moveFile(directory: string, file: string, to: string) {
await this.service.sendPutRequest("files/move/file?directory=" + directory + "&file=" + file + "&to=" + to, null, {authorized: true});
}
public toFile(content: string, name: string): File {
const blob = new Blob([content], {type: "text/plain"});
return new File([blob], name, {type: "text/plain"});
}
}

View File

@@ -0,0 +1,20 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SettingsService {
public static eventChange: CustomEvent = new CustomEvent<any>("settings_change");
constructor() { }
public setSetting(key: string, value: any) {
localStorage.setItem(key, JSON.stringify(value));
document.dispatchEvent(SettingsService.eventChange);
}
public getSetting<T>(key: string, defaultValue?: T): T {
return JSON.parse(localStorage.getItem(key)) || defaultValue;
}
}

View File

@@ -0,0 +1,160 @@
import { Injectable } from '@angular/core';
import { CrudService } from '../crud.service';
import { Token } from '../entitys/token';
import { User, UserEditor, UserLogin } from '../entitys/user';
@Injectable({
providedIn: 'root'
})
export class UserAPI {
private user: User;
constructor(private service: CrudService) { }
async login(login: UserLogin): Promise<boolean> {
try {
const accessToken = await this.service.sendPutRequest<Token>("users/login", JSON.stringify(login), {withCredentials: true});
this.service.setAccessToken(accessToken.id);
return true;
}catch(err) {
return false;
}
}
async register(editor: UserEditor): Promise<boolean> {
try {
const accessToken = await this.service.sendPostRequest<Token>("users/register", JSON.stringify(editor), {withCredentials: true});
this.service.setAccessToken(accessToken.id);
return true;
}catch {
return false;
}
}
async logout(): Promise<void> {
await this.service.sendDeleteRequest("users/logout", {authorized: true, withCredentials: true});
}
async editUser(id: string, editor: UserEditor): Promise<boolean> {
try {
await this.service.sendPutRequest("users/" + id, JSON.stringify(editor), {authorized: true});
return true;
}catch {
return false;
}
}
async editOwnUser(editor: UserEditor) {
try {
await this.service.sendPutRequest("users/ownuser", JSON.stringify(editor), {authorized: true});
return true;
}catch {
return false;
}
}
async deleteOwnUser() {
try {
await this.service.sendDeleteRequest("users/ownuser", {authorized: true, withCredentials: true});
localStorage.clear();
return true;
}catch {
return false;
}
}
async deleteUser(id: string): Promise<boolean> {
try {
await this.service.sendDeleteRequest("users/" + id, {authorized: true});
return true;
}catch {
return false;
}
}
async getUser(data: {id?: string, username?: string, email?: string}): Promise<User | null> {
if (data.id !== undefined) {
return this.service.sendGetRequest("users/" + data.id);
}
const users = await this.getUsers();
if (data.username !== undefined) {
return users.find(user => user.username == data.username) as User;
}
if (data.email !== undefined) {
return users.find(user => user.email == data.email) as User;
}
return null;
}
async getUsers(): Promise<User[]> {
return this.service.sendGetRequest<User[]>("users");
}
async isLoggedIn(): Promise<boolean> {
try {
return await this.service.sendGetRequest("users/validate", {authorized: true, withCredentials: true, dontResendOnExpiration: true, returnNewTokenResponse: true}) === true;
}catch {
return false;
}
}
async loadUser(): Promise<boolean> {
if (!(await this.isLoggedIn())) return false;
this.user = await this.service.sendGetRequest<User>("users/ownuser", {authorized: true});
this.user.created = new Date(this.user.created.toString());
return true;
}
async checkForPermission(permission: string): Promise<boolean> {
await this.getCurrentUser();
const permissions = await this.service.sendGetRequest<string[]>("users/permissions", {authorized: true});
if (permissions.includes("*") || permissions.includes(permission))
return true;
const splice = permission.split(".");
let builder: string = "";
for (let pp of splice) {
builder += pp + ".";
if (permissions.includes(builder + "*"))
return true;
}
return false;
}
async getOwnPermissions(): Promise<string[]> {
await this.getCurrentUser();
return this.service.sendGetRequest<string[]>("users/permissions", {authorized: true});
}
async getPermissions(id: string): Promise<string[]> {
return this.service.sendGetRequest<string[]>("users/" + id + "/permissions/raw", {authorized: true});
}
async addPermission(id: string, permission: string): Promise<boolean> {
try {
await this.service.sendPostRequest("users/" + id + "/permissions/" + permission, undefined, {authorized: true});
return true;
}catch {
return false;
}
}
async removePermission(id: string, permission: string): Promise<boolean> {
try {
await this.service.sendDeleteRequest("users/" + id + "/permissions/" + permission, {authorized: true});
return true;
}catch {
return false;
}
}
public async getCurrentUser(forceReload: boolean = false): Promise<User> {
if (this.user != undefined && !forceReload)
return this.user;
else {
await this.loadUser();
return this.user;
}
}
}

View File

@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from "./login/login.component";
import { Desktop } from "./desktop/desktop.component";
import { RegisterComponent } from './register/register.component';
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: '', component: Desktop },
{ path: '*', redirectTo: '' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -0,0 +1 @@
<router-outlet *ngIf="ready"></router-outlet>

Some files were not shown because too many files have changed in this diff Show More