diff --git a/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs b/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs
index 4955f81..02636cf 100644
--- a/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs
+++ b/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
+using MoonlightServers.ApiServer.Models;
namespace MoonlightServers.ApiServer.Database.Entities;
@@ -9,8 +10,7 @@ public class ServerShare
public int UserId { get; set; }
public Server Server { get; set; }
- [Column(TypeName = "jsonb")]
- public string Permissions { get; set; }
+ public ServerShareContent Content { get; set; } = new();
[Column(TypeName="timestamp with time zone")]
public DateTime CreatedAt { get; set; }
diff --git a/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs b/MoonlightServers.ApiServer/Database/Migrations/20250606121013_AddedShares.Designer.cs
similarity index 89%
rename from MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs
rename to MoonlightServers.ApiServer/Database/Migrations/20250606121013_AddedShares.Designer.cs
index 8abcf77..570c6b5 100644
--- a/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs
+++ b/MoonlightServers.ApiServer/Database/Migrations/20250606121013_AddedShares.Designer.cs
@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace MoonlightServers.ApiServer.Database.Migrations
{
[DbContext(typeof(ServersDataContext))]
- [Migration("20250605210823_AddedServerShares")]
- partial class AddedServerShares
+ [Migration("20250606121013_AddedShares")]
+ partial class AddedShares
{
///
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -194,10 +194,6 @@ namespace MoonlightServers.ApiServer.Database.Migrations
b.Property("CreatedAt")
.HasColumnType("timestamp with time zone");
- b.Property("Permissions")
- .IsRequired()
- .HasColumnType("jsonb");
-
b.Property("ServerId")
.HasColumnType("integer");
@@ -434,6 +430,50 @@ namespace MoonlightServers.ApiServer.Database.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
+ b.OwnsOne("MoonlightServers.ApiServer.Models.ServerShareContent", "Content", b1 =>
+ {
+ b1.Property("ServerShareId")
+ .HasColumnType("integer");
+
+ b1.HasKey("ServerShareId");
+
+ b1.ToTable("Servers_ServerShares");
+
+ b1.ToJson("Content");
+
+ b1.WithOwner()
+ .HasForeignKey("ServerShareId");
+
+ b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerSharePermission", "Permissions", b2 =>
+ {
+ b2.Property("ServerShareContentServerShareId")
+ .HasColumnType("integer");
+
+ b2.Property("__synthesizedOrdinal")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ b2.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b2.Property("Type")
+ .HasColumnType("integer");
+
+ b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
+
+ b2.ToTable("Servers_ServerShares");
+
+ b2.WithOwner()
+ .HasForeignKey("ServerShareContentServerShareId");
+ });
+
+ b1.Navigation("Permissions");
+ });
+
+ b.Navigation("Content")
+ .IsRequired();
+
b.Navigation("Server");
});
diff --git a/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs b/MoonlightServers.ApiServer/Database/Migrations/20250606121013_AddedShares.cs
similarity index 91%
rename from MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs
rename to MoonlightServers.ApiServer/Database/Migrations/20250606121013_AddedShares.cs
index 25d4cfc..fe5dd2c 100644
--- a/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs
+++ b/MoonlightServers.ApiServer/Database/Migrations/20250606121013_AddedShares.cs
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace MoonlightServers.ApiServer.Database.Migrations
{
///
- public partial class AddedServerShares : Migration
+ public partial class AddedShares : Migration
{
///
protected override void Up(MigrationBuilder migrationBuilder)
@@ -20,9 +20,9 @@ namespace MoonlightServers.ApiServer.Database.Migrations
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column(type: "integer", nullable: false),
ServerId = table.Column(type: "integer", nullable: false),
- Permissions = table.Column(type: "jsonb", nullable: false),
CreatedAt = table.Column(type: "timestamp with time zone", nullable: false),
- UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false)
+ UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false),
+ Content = table.Column(type: "jsonb", nullable: false)
},
constraints: table =>
{
diff --git a/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs b/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs
index 9136078..e136036 100644
--- a/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs
+++ b/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs
@@ -191,10 +191,6 @@ namespace MoonlightServers.ApiServer.Database.Migrations
b.Property("CreatedAt")
.HasColumnType("timestamp with time zone");
- b.Property("Permissions")
- .IsRequired()
- .HasColumnType("jsonb");
-
b.Property("ServerId")
.HasColumnType("integer");
@@ -431,6 +427,50 @@ namespace MoonlightServers.ApiServer.Database.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
+ b.OwnsOne("MoonlightServers.ApiServer.Models.ServerShareContent", "Content", b1 =>
+ {
+ b1.Property("ServerShareId")
+ .HasColumnType("integer");
+
+ b1.HasKey("ServerShareId");
+
+ b1.ToTable("Servers_ServerShares");
+
+ b1.ToJson("Content");
+
+ b1.WithOwner()
+ .HasForeignKey("ServerShareId");
+
+ b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerSharePermission", "Permissions", b2 =>
+ {
+ b2.Property("ServerShareContentServerShareId")
+ .HasColumnType("integer");
+
+ b2.Property("__synthesizedOrdinal")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ b2.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b2.Property("Type")
+ .HasColumnType("integer");
+
+ b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
+
+ b2.ToTable("Servers_ServerShares");
+
+ b2.WithOwner()
+ .HasForeignKey("ServerShareContentServerShareId");
+ });
+
+ b1.Navigation("Permissions");
+ });
+
+ b.Navigation("Content")
+ .IsRequired();
+
b.Navigation("Server");
});
diff --git a/MoonlightServers.ApiServer/Database/ServersDataContext.cs b/MoonlightServers.ApiServer/Database/ServersDataContext.cs
index b34fbd4..625fe82 100644
--- a/MoonlightServers.ApiServer/Database/ServersDataContext.cs
+++ b/MoonlightServers.ApiServer/Database/ServersDataContext.cs
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
using MoonCore.Extended.SingleDb;
using Moonlight.ApiServer.Configuration;
using MoonlightServers.ApiServer.Database.Entities;
+using MoonlightServers.ApiServer.Models;
namespace MoonlightServers.ApiServer.Database;
@@ -30,4 +31,26 @@ public class ServersDataContext : DatabaseContext
Database = configuration.Database.Database
};
}
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ #region Shares
+
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ modelBuilder.Entity(builder =>
+ {
+ builder.OwnsOne(x => x.Content, navigationBuilder =>
+ {
+ navigationBuilder.ToJson();
+
+ navigationBuilder.OwnsMany(x => x.Permissions);
+ });
+ });
+
+ #endregion
+ }
}
\ No newline at end of file
diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs
index 9a13198..c0a130f 100644
--- a/MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs
+++ b/MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs
@@ -164,8 +164,13 @@ public class FilesController : Controller
if (server == null)
throw new HttpApiException("No server with this id found", 404);
- if (!await AuthorizeService.Authorize(User, server, permission => permission.Name == "files" && permission.Type >= type))
- throw new HttpApiException("No server with this id found", 404);
+ var authorizeResult = await AuthorizeService.Authorize(
+ User, server,
+ permission => permission.Name == "files" && permission.Type >= type
+ );
+
+ if (!authorizeResult.Succeeded)
+ throw new HttpApiException("No permission for the requested resource", 403);
return server;
}
diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs
index 3f6364d..19dc6dd 100644
--- a/MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs
+++ b/MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs
@@ -68,9 +68,14 @@ public class PowerController : Controller
if (server == null)
throw new HttpApiException("No server with this id found", 404);
- if (!await AuthorizeService.Authorize(User, server, permission => permission is { Name: "power", Type: ServerPermissionType.ReadWrite }))
- throw new HttpApiException("No server with this id found", 404);
+ var authorizeResult = await AuthorizeService.Authorize(
+ User, server,
+ permission => permission.Name == "power" && permission.Type >= ServerPermissionType.ReadWrite
+ );
+ if (!authorizeResult.Succeeded)
+ throw new HttpApiException("No permission for the requested resource", 403);
+
return server;
}
}
\ No newline at end of file
diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs
index e0ba7ae..d5553b6 100644
--- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs
+++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs
@@ -148,7 +148,9 @@ public class ServersController : Controller
if (server == null)
throw new HttpApiException("No server with this id found", 404);
- if (!await AuthorizeService.Authorize(User, server))
+ var authorizationResult = await AuthorizeService.Authorize(User, server);
+
+ if (!authorizationResult.Succeeded)
throw new HttpApiException("No server with this id found", 404);
return new ServerDetailResponse()
@@ -256,8 +258,10 @@ public class ServersController : Controller
if (server == null)
throw new HttpApiException("No server with this id found", 404);
- if (!await AuthorizeService.Authorize(User, server, filter))
- throw new HttpApiException("No server with this id found", 404);
+ var authorizeResult = await AuthorizeService.Authorize(User, server, filter);
+
+ if (!authorizeResult.Succeeded)
+ throw new HttpApiException("No permission for the requested resource", 403);
return server;
}
diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs
index ca1e7b8..89b0cbf 100644
--- a/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs
+++ b/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs
@@ -47,9 +47,14 @@ public class SettingsController : Controller
if (server == null)
throw new HttpApiException("No server with this id found", 404);
- if (!await AuthorizeService.Authorize(User, server, permission => permission is { Name: "settings", Type: ServerPermissionType.ReadWrite }))
- throw new HttpApiException("No server with this id found", 404);
+ var authorizeResult = await AuthorizeService.Authorize(
+ User, server,
+ permission => permission is { Name: "settings", Type: >= ServerPermissionType.ReadWrite }
+ );
+ if (!authorizeResult.Succeeded)
+ throw new HttpApiException("No permission for the requested resource", 403);
+
return server;
}
}
\ No newline at end of file
diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs
index ba30513..b4f3eab 100644
--- a/MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs
+++ b/MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs
@@ -132,9 +132,13 @@ public class VariablesController : Controller
if (server == null)
throw new HttpApiException("No server with this id found", 404);
- if (!await AuthorizeService.Authorize(User, server,
- permission => permission.Name == "variables" && permission.Type >= type))
- throw new HttpApiException("No server with this id found", 404);
+ var authorizeResult = await AuthorizeService.Authorize(
+ User, server,
+ permission => permission.Name == "variables" && permission.Type >= type
+ );
+
+ if (!authorizeResult.Succeeded)
+ throw new HttpApiException("No permission for the requested resource", 403);
return server;
}
diff --git a/MoonlightServers.ApiServer/Models/ServerShareContent.cs b/MoonlightServers.ApiServer/Models/ServerShareContent.cs
new file mode 100644
index 0000000..be98268
--- /dev/null
+++ b/MoonlightServers.ApiServer/Models/ServerShareContent.cs
@@ -0,0 +1,6 @@
+namespace MoonlightServers.ApiServer.Models;
+
+public class ServerShareContent
+{
+ public List Permissions { get; set; } = [];
+}
\ No newline at end of file
diff --git a/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs b/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs
index a233068..07cbf23 100644
--- a/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs
+++ b/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs
@@ -24,50 +24,54 @@ public class ServerAuthorizeService
ShareRepository = shareRepository;
}
- public async Task Authorize(ClaimsPrincipal user, Server server, Func? filter = null)
+ public async Task Authorize(ClaimsPrincipal user, Server server, Func? filter = null)
{
var userIdClaim = user.FindFirst("userId");
// User specific authorization
- if (userIdClaim != null && await AuthorizeViaUser(userIdClaim, server, filter))
- return true;
+ if (userIdClaim != null)
+ {
+ var result = await AuthorizeViaUser(userIdClaim, server, filter);
+
+ if (result.Succeeded)
+ return result;
+ }
// Permission specific authorization
return await AuthorizeViaPermission(user);
}
- private async Task AuthorizeViaUser(Claim userIdClaim, Server server, Func? filter = null)
+ private async Task AuthorizeViaUser(Claim userIdClaim, Server server, Func? filter = null)
{
var userId = int.Parse(userIdClaim.Value);
if (server.OwnerId == userId)
- return true;
+ return AuthorizationResult.Success();
var possibleShare = await ShareRepository
.Get()
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.UserId == userId);
if (possibleShare == null)
- return false;
+ return AuthorizationResult.Failed();
// If no filter has been specified every server share is valid
// no matter which permission the share actually has
if (filter == null)
- return true;
+ return AuthorizationResult.Success();
- var permissionsOfShare = ParsePermissions(possibleShare.Permissions);
+ if(possibleShare.Content.Permissions.Any(filter))
+ return AuthorizationResult.Success();
- return permissionsOfShare.Any(filter);
+ return AuthorizationResult.Failed();
}
- private async Task AuthorizeViaPermission(ClaimsPrincipal user)
+ private async Task AuthorizeViaPermission(ClaimsPrincipal user)
{
- var authorizeResult = await AuthorizationService.AuthorizeAsync(
+ return await AuthorizationService.AuthorizeAsync(
user,
"permissions:admin.servers.get"
);
-
- return authorizeResult.Succeeded;
}
private ServerSharePermission[] ParsePermissions(string permissionsString)
@@ -96,34 +100,4 @@ public class ServerAuthorizeService
return result.ToArray();
}
-
- private bool CheckSharePermission(ServerShare share, string permission, ServerPermissionType type)
- {
- if (string.IsNullOrEmpty(share.Permissions))
- return false;
-
- var permissions = share.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries);
-
- foreach (var sharePermission in permissions)
- {
- if (!sharePermission.StartsWith(permission))
- continue;
-
- var typeParts = sharePermission.Split(':', StringSplitOptions.RemoveEmptyEntries);
-
- // Missing permission type
- if (typeParts.Length != 2)
- return false;
-
- // Parse type id
- if (!int.TryParse(typeParts[1], out var typeId))
- return false; // Malformed
-
- var requiredId = (int)type;
-
- return typeId >= requiredId;
- }
-
- return false;
- }
}
\ No newline at end of file