mirror of
https://github.com/sbrl/Nibriboard.git
synced 2018-01-10 21:33:49 +00:00
[server] Bugfix core user/rbac + manager data structures
This commit is contained in:
parent
def3c2f003
commit
193421261a
7 changed files with 412 additions and 96 deletions
|
@ -1,11 +1,13 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Nibriboard.Client;
|
||||
using Nibriboard.RippleSpace;
|
||||
using Nibriboard.Userspace;
|
||||
using Nibriboard.Utilities;
|
||||
|
||||
namespace Nibriboard
|
||||
|
@ -55,116 +57,50 @@ namespace Nibriboard
|
|||
}
|
||||
}
|
||||
|
||||
private async Task executeCommand(StreamWriter destination, string[] commandParts)
|
||||
private async Task executeCommand(StreamWriter dest, string[] commandParts)
|
||||
{
|
||||
string commandName = commandParts[0].Trim();
|
||||
switch(commandName)
|
||||
{
|
||||
case "help":
|
||||
await destination.WriteLineAsync("Nibriboard Server Command Console");
|
||||
await destination.WriteLineAsync("=================================");
|
||||
await destination.WriteLineAsync("Available commands:");
|
||||
await destination.WriteLineAsync(" help Show this message");
|
||||
await destination.WriteLineAsync(" version Show the version of nibriboard that is currently running");
|
||||
await destination.WriteLineAsync(" save Save the ripplespace to disk");
|
||||
await destination.WriteLineAsync(" plane {subcommand} Interact with planes");
|
||||
await destination.WriteLineAsync(" clients List the currently connected clients");
|
||||
await dest.WriteLineAsync("Nibriboard Server Command Console");
|
||||
await dest.WriteLineAsync("=================================");
|
||||
await dest.WriteLineAsync("Available commands:");
|
||||
await dest.WriteLineAsync(" help Show this message");
|
||||
await dest.WriteLineAsync(" version Show the version of nibriboard that is currently running");
|
||||
await dest.WriteLineAsync(" save Save the ripplespace to disk");
|
||||
await dest.WriteLineAsync(" plane {subcommand} Interact with planes");
|
||||
await dest.WriteLineAsync(" users Interact with user accounts");
|
||||
await dest.WriteLineAsync(" clients List the currently connected clients");
|
||||
break;
|
||||
case "version":
|
||||
await destination.WriteLineAsync($"Nibriboard Server {NibriboardServer.Version}, built on {NibriboardServer.BuildDate.ToString("R")}");
|
||||
await destination.WriteLineAsync("By Starbeamrainbowlabs, licensed under MPL-2.0");
|
||||
await dest.WriteLineAsync($"Nibriboard Server {NibriboardServer.Version}, built on {NibriboardServer.BuildDate.ToString("R")}");
|
||||
await dest.WriteLineAsync("By Starbeamrainbowlabs, licensed under MPL-2.0");
|
||||
break;
|
||||
case "save":
|
||||
await destination.WriteAsync("Saving ripple space - ");
|
||||
await dest.WriteAsync("Saving ripple space - ");
|
||||
Stopwatch timer = Stopwatch.StartNew();
|
||||
long bytesWritten = await server.PlaneManager.Save();
|
||||
long msTaken = timer.ElapsedMilliseconds;
|
||||
await destination.WriteLineAsync("done.");
|
||||
await destination.WriteLineAsync($"{Formatters.HumanSize(bytesWritten)} written in {msTaken}ms.");
|
||||
await destination.WriteLineAsync($"Save is now {Formatters.HumanSize(server.PlaneManager.LastSaveSize)} in size.");
|
||||
await dest.WriteLineAsync("done.");
|
||||
await dest.WriteLineAsync($"{Formatters.HumanSize(bytesWritten)} written in {msTaken}ms.");
|
||||
await dest.WriteLineAsync($"Save is now {Formatters.HumanSize(server.PlaneManager.LastSaveSize)} in size.");
|
||||
break;
|
||||
case "plane":
|
||||
if(commandParts.Length < 2) {
|
||||
await destination.WriteLineAsync("Nibriboard Server Command Console: plane");
|
||||
await destination.WriteLineAsync("----------------------------------------");
|
||||
await destination.WriteLineAsync("Interact with planes.");
|
||||
await destination.WriteLineAsync("Usage:");
|
||||
await destination.WriteLineAsync(" plane {subcommand}");
|
||||
await destination.WriteLineAsync();
|
||||
await destination.WriteLineAsync("Subcommands:");
|
||||
await destination.WriteLineAsync(" list");
|
||||
await destination.WriteLineAsync(" List all the currently loaded planes");
|
||||
await destination.WriteLineAsync(" create {new-plane-name} [{chunkSize}]");
|
||||
await destination.WriteLineAsync(" Create a new named plane, optionally with the specified chunk size");
|
||||
await destination.WriteLineAsync(" status {plane-name}");
|
||||
await destination.WriteLineAsync(" Show the statistics of the specified plane");
|
||||
break;
|
||||
}
|
||||
string subAction = commandParts[1].Trim();
|
||||
switch(subAction)
|
||||
{
|
||||
case "list":
|
||||
await destination.WriteLineAsync("Planes:");
|
||||
foreach(Plane plane in server.PlaneManager.Planes)
|
||||
await destination.WriteLineAsync($" {plane.Name} @ {plane.ChunkSize} ({plane.LoadedChunks} / ~{plane.SoftLoadedChunkLimit} chunks loaded, {plane.UnloadableChunks} inactive, {plane.TotalChunks} total at last save)");
|
||||
await destination.WriteLineAsync();
|
||||
await destination.WriteLineAsync($"Total {server.PlaneManager.Planes.Count}");
|
||||
break;
|
||||
case "create":
|
||||
if(commandParts.Length < 3) {
|
||||
await destination.WriteLineAsync("Error: No name specified for the new plane!");
|
||||
return;
|
||||
}
|
||||
string newPlaneName = commandParts[2].Trim();
|
||||
int chunkSize = server.PlaneManager.DefaultChunkSize;
|
||||
if(commandParts.Length >= 4)
|
||||
chunkSize = int.Parse(commandParts[3]);
|
||||
|
||||
server.PlaneManager.CreatePlane(new PlaneInfo(
|
||||
newPlaneName,
|
||||
chunkSize
|
||||
));
|
||||
|
||||
await destination.WriteLineAsync($"Created plane with name {newPlaneName} and chunk size {chunkSize}.");
|
||||
|
||||
break;
|
||||
case "status":
|
||||
if(commandParts.Length < 3) {
|
||||
await destination.WriteLineAsync("Error: No plane name specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
string targetPlaneName = commandParts[2].Trim();
|
||||
Plane targetPlane = server.PlaneManager.GetByName(targetPlaneName);
|
||||
if(targetPlane == null) {
|
||||
await destination.WriteLineAsync($"Error: A plane with the name {targetPlaneName} doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
await destination.WriteLineAsync($"Name: {targetPlane.Name}");
|
||||
await destination.WriteLineAsync($"Chunk size: {targetPlane.ChunkSize}");
|
||||
await destination.WriteLineAsync($"Loaded chunks: {targetPlane.LoadedChunks}");
|
||||
await destination.WriteLineAsync($"Unloaded chunks: {targetPlane.TotalChunks - targetPlane.LoadedChunks}");
|
||||
await destination.WriteLineAsync($"Total chunks: {targetPlane.TotalChunks}");
|
||||
await destination.WriteLineAsync($"Primary chunk area size: {targetPlane.PrimaryChunkAreaSize}");
|
||||
await destination.WriteLineAsync($"Min unloadeable chunks: {targetPlane.MinUnloadeableChunks}");
|
||||
await destination.WriteLineAsync($"Soft loaded chunk limit: {targetPlane.SoftLoadedChunkLimit}");
|
||||
|
||||
break;
|
||||
default:
|
||||
await destination.WriteLineAsync($"Error: Unknown sub-action {subAction}.");
|
||||
break;
|
||||
}
|
||||
await handlePlaneCommand(commandParts, dest);
|
||||
break;
|
||||
|
||||
case "clients":
|
||||
|
||||
foreach(NibriClient client in server.AppServer.NibriClients) {
|
||||
await destination.WriteLineAsync($"{client.Id}: {client.Name} from {client.RemoteEndpoint}, on {client.CurrentPlane.Name} looking at {client.CurrentViewPort}");
|
||||
await dest.WriteLineAsync($"{client.Id}: {client.Name} from {client.RemoteEndpoint}, on {client.CurrentPlane.Name} looking at {client.CurrentViewPort}");
|
||||
}
|
||||
await destination.WriteLineAsync();
|
||||
await destination.WriteLineAsync($"Total {server.AppServer.ClientCount} clients");
|
||||
await dest.WriteLineAsync();
|
||||
await dest.WriteLineAsync($"Total {server.AppServer.ClientCount} clients");
|
||||
break;
|
||||
|
||||
case "users":
|
||||
await handleUsersCommand(commandParts, dest);
|
||||
break;
|
||||
|
||||
/*case "chunk":
|
||||
|
@ -195,9 +131,243 @@ namespace Nibriboard
|
|||
break;*/
|
||||
|
||||
default:
|
||||
await destination.WriteLineAsync($"Error: Unrecognised command {commandName}");
|
||||
await dest.WriteLineAsync($"Error: Unrecognised command {commandName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task handlePlaneCommand(string[] commandParts, StreamWriter dest)
|
||||
{
|
||||
if (commandParts.Length < 2)
|
||||
{
|
||||
await dest.WriteLineAsync("Nibriboard Server Command Console: plane");
|
||||
await dest.WriteLineAsync("----------------------------------------");
|
||||
await dest.WriteLineAsync("Interact with planes.");
|
||||
await dest.WriteLineAsync("Usage:");
|
||||
await dest.WriteLineAsync(" plane {subcommand}");
|
||||
await dest.WriteLineAsync();
|
||||
await dest.WriteLineAsync("Subcommands:");
|
||||
await dest.WriteLineAsync(" list");
|
||||
await dest.WriteLineAsync(" List all the currently loaded planes");
|
||||
await dest.WriteLineAsync(" create {new-plane-name} [{chunkSize}]");
|
||||
await dest.WriteLineAsync(" Create a new named plane, optionally with the specified chunk size");
|
||||
await dest.WriteLineAsync(" status {plane-name}");
|
||||
await dest.WriteLineAsync(" Show the statistics of the specified plane");
|
||||
return;
|
||||
}
|
||||
string subAction = commandParts[1].Trim();
|
||||
switch (subAction)
|
||||
{
|
||||
case "list":
|
||||
await dest.WriteLineAsync("Planes:");
|
||||
foreach (Plane plane in server.PlaneManager.Planes)
|
||||
await dest.WriteLineAsync($" {plane.Name} @ {plane.ChunkSize} ({plane.LoadedChunks} / ~{plane.SoftLoadedChunkLimit} chunks loaded, {plane.UnloadableChunks} inactive, {plane.TotalChunks} total at last save)");
|
||||
await dest.WriteLineAsync();
|
||||
await dest.WriteLineAsync($"Total {server.PlaneManager.Planes.Count}");
|
||||
break;
|
||||
case "create":
|
||||
if (commandParts.Length < 3)
|
||||
{
|
||||
await dest.WriteLineAsync("Error: No name specified for the new plane!");
|
||||
return;
|
||||
}
|
||||
string newPlaneName = commandParts[2].Trim();
|
||||
int chunkSize = server.PlaneManager.DefaultChunkSize;
|
||||
if (commandParts.Length >= 4)
|
||||
chunkSize = int.Parse(commandParts[3]);
|
||||
|
||||
server.PlaneManager.CreatePlane(new PlaneInfo(
|
||||
newPlaneName,
|
||||
chunkSize
|
||||
));
|
||||
|
||||
await dest.WriteLineAsync($"Created plane with name {newPlaneName} and chunk size {chunkSize}.");
|
||||
|
||||
break;
|
||||
case "status":
|
||||
if (commandParts.Length < 3)
|
||||
{
|
||||
await dest.WriteLineAsync("Error: No plane name specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
string targetPlaneName = commandParts[2].Trim();
|
||||
Plane targetPlane = server.PlaneManager.GetByName(targetPlaneName);
|
||||
if (targetPlane == null)
|
||||
{
|
||||
await dest.WriteLineAsync($"Error: A plane with the name {targetPlaneName} doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
await dest.WriteLineAsync($"Name: {targetPlane.Name}");
|
||||
await dest.WriteLineAsync($"Chunk size: {targetPlane.ChunkSize}");
|
||||
await dest.WriteLineAsync($"Loaded chunks: {targetPlane.LoadedChunks}");
|
||||
await dest.WriteLineAsync($"Unloaded chunks: {targetPlane.TotalChunks - targetPlane.LoadedChunks}");
|
||||
await dest.WriteLineAsync($"Total chunks: {targetPlane.TotalChunks}");
|
||||
await dest.WriteLineAsync($"Primary chunk area size: {targetPlane.PrimaryChunkAreaSize}");
|
||||
await dest.WriteLineAsync($"Min unloadeable chunks: {targetPlane.MinUnloadeableChunks}");
|
||||
await dest.WriteLineAsync($"Soft loaded chunk limit: {targetPlane.SoftLoadedChunkLimit}");
|
||||
|
||||
break;
|
||||
default:
|
||||
await dest.WriteLineAsync($"Error: Unknown sub-action {subAction}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task handleUsersCommand(string[] commandParts, StreamWriter dest)
|
||||
{
|
||||
if (commandParts.Length < 2)
|
||||
{
|
||||
await dest.WriteLineAsync("Nibriboard Server Command Console: users");
|
||||
await dest.WriteLineAsync("----------------------------------------");
|
||||
await dest.WriteLineAsync("Interact with user accounts.");
|
||||
await dest.WriteLineAsync("Usage:");
|
||||
await dest.WriteLineAsync(" users {subcommand}");
|
||||
await dest.WriteLineAsync();
|
||||
await dest.WriteLineAsync("Subcommands:");
|
||||
await dest.WriteLineAsync(" list");
|
||||
await dest.WriteLineAsync(" Lists all users.");
|
||||
await dest.WriteLineAsync(" add {username} {password}");
|
||||
await dest.WriteLineAsync(" Adds a new user");
|
||||
await dest.WriteLineAsync(" roles list");
|
||||
await dest.WriteLineAsync(" Lists all roles");
|
||||
await dest.WriteLineAsync(" roles grant {role-name} {username}");
|
||||
await dest.WriteLineAsync(" Adds a role to a user");
|
||||
await dest.WriteLineAsync(" roles revoke {role-name} {username}");
|
||||
await dest.WriteLineAsync(" Removes a role from a user");
|
||||
return;
|
||||
}
|
||||
|
||||
string subAction = commandParts[1].Trim();
|
||||
switch (subAction)
|
||||
{
|
||||
case "list":
|
||||
await dest.WriteLineAsync(
|
||||
string.Join("\n", server.AccountManager.Users.Select(
|
||||
(User user) => $"{user.CreationTime}\t{user.Username}\t{string.Join(", ", user.Roles.Select((RbacRole role) => role.Name))}"
|
||||
))
|
||||
);
|
||||
break;
|
||||
case "add":
|
||||
string newUsername = (commandParts[2] ?? "").Trim();
|
||||
string password = (commandParts[3] ?? "").Trim();
|
||||
|
||||
if (newUsername.Length == 0) {
|
||||
await dest.WriteLineAsync("Error: No username specified!");
|
||||
break;
|
||||
}
|
||||
if (password.Length == 0) {
|
||||
await dest.WriteLineAsync("Error: No password specified!");
|
||||
break;
|
||||
}
|
||||
|
||||
server.AccountManager.AddUser(newUsername, password);
|
||||
await server.SaveUserData();
|
||||
await dest.WriteLineAsync($"Added user with name {newUsername} successfully.");
|
||||
break;
|
||||
case "roles":
|
||||
await handleRoleCommand(commandParts, dest);
|
||||
break;
|
||||
default:
|
||||
await dest.WriteLineAsync($"Unrecognised sub-command {subAction}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task handleRoleCommand(string[] commandParts, StreamWriter dest)
|
||||
{
|
||||
string subAction = (commandParts[2] ?? "").Trim();
|
||||
if (subAction.Length == 0)
|
||||
{
|
||||
await dest.WriteLineAsync($"Error: No sub-action specified.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subAction)
|
||||
{
|
||||
case "list":
|
||||
await dest.WriteLineAsync(string.Join("\n", server.AccountManager.Roles.Select(
|
||||
(RbacRole role) => role.ToString()
|
||||
)));
|
||||
break;
|
||||
case "grant":
|
||||
await handleRoleGrantCommand(commandParts, dest);
|
||||
break;
|
||||
|
||||
case "revoke":
|
||||
await handleRoleRevokeCommand(commandParts, dest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task handleRoleGrantCommand(string[] commandParts, StreamWriter dest)
|
||||
{
|
||||
|
||||
string roleName = (commandParts[3] ?? "").Trim();
|
||||
string recievingUsername = (commandParts[4] ?? "").Trim();
|
||||
if (roleName.Length == 0) {
|
||||
await dest.WriteLineAsync("Error: No role name specified!");
|
||||
return;
|
||||
}
|
||||
if (recievingUsername.Length == 0) {
|
||||
await dest.WriteLineAsync("Error: No username specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
User user = server.AccountManager.GetByName(recievingUsername);
|
||||
RbacRole roleToGrant = server.AccountManager.ResolveRole(roleName);
|
||||
if (user == null) {
|
||||
await dest.WriteLineAsync($"Error: No user with the the name {recievingUsername} could be found.");
|
||||
return;
|
||||
}
|
||||
if (roleToGrant == null) {
|
||||
await dest.WriteLineAsync($"Error: No role with the the name {roleName} could be found.");
|
||||
return;
|
||||
}
|
||||
if (user.HasRole(roleToGrant)) {
|
||||
await dest.WriteLineAsync($"Error: {recievingUsername} already has the role {roleToGrant.Name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
user.Roles.Add(roleToGrant);
|
||||
await server.SaveUserData();
|
||||
|
||||
await dest.WriteLineAsync($"Role {roleToGrant.Name} added to {user.Username} successfully.");
|
||||
}
|
||||
private async Task handleRoleRevokeCommand(string[] commandParts, StreamWriter dest)
|
||||
{
|
||||
|
||||
string roleName = (commandParts[3] ?? "").Trim();
|
||||
string recievingUsername = (commandParts[4] ?? "").Trim();
|
||||
if (roleName.Length == 0) {
|
||||
await dest.WriteLineAsync("Error: No role name specified!");
|
||||
return;
|
||||
}
|
||||
if (recievingUsername.Length == 0) {
|
||||
await dest.WriteLineAsync("Error: No username specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
User user = server.AccountManager.GetByName(recievingUsername);
|
||||
RbacRole roleToGrant = server.AccountManager.ResolveRole(roleName);
|
||||
if (user == null) {
|
||||
await dest.WriteLineAsync($"Error: No user with the the name {recievingUsername} could be found.");
|
||||
return;
|
||||
}
|
||||
if (roleToGrant == null) {
|
||||
await dest.WriteLineAsync($"Error: No role with the the name {roleName} could be found.");
|
||||
return;
|
||||
}
|
||||
if (!user.HasRole(roleToGrant)) {
|
||||
await dest.WriteLineAsync($"Error: {recievingUsername} doesn't have the role {roleToGrant.Name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
user.Roles.Remove(roleToGrant);
|
||||
await server.SaveUserData();
|
||||
|
||||
await dest.WriteLineAsync($"Role {roleToGrant.Name} removed from {user.Username} successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ using Nibriboard.RippleSpace;
|
|||
using Nibriboard.Client;
|
||||
using System.Reflection;
|
||||
using SBRL.Utilities;
|
||||
using Nibriboard.Userspace;
|
||||
using Nibriboard.Utilities;
|
||||
|
||||
namespace Nibriboard
|
||||
{
|
||||
|
@ -46,6 +48,7 @@ namespace Nibriboard
|
|||
public readonly int CommandPort = 31587;
|
||||
public readonly int Port = 31586;
|
||||
|
||||
public readonly UserManager AccountManager;
|
||||
public readonly RippleSpaceManager PlaneManager;
|
||||
public readonly NibriboardApp AppServer;
|
||||
|
||||
|
@ -62,6 +65,13 @@ namespace Nibriboard
|
|||
PlaneManager = new RippleSpaceManager(pathToRippleSpace);
|
||||
}
|
||||
|
||||
// Next, load the user account data
|
||||
string accountDataPath = CalcPaths.RippleSpaceAccountData(pathToRippleSpace);
|
||||
AccountManager = new UserManager();
|
||||
if(File.Exists(accountDataPath))
|
||||
AccountManager.LoadUserDataFile(CalcPaths.RippleSpaceAccountData(pathToRippleSpace)).Wait();
|
||||
|
||||
|
||||
clientSettings = new ClientSettings() {
|
||||
SecureWebSocket = false,
|
||||
WebSocketHost = "192.168.0.56",
|
||||
|
@ -100,5 +110,12 @@ namespace Nibriboard
|
|||
{
|
||||
await commandServer.Start();
|
||||
}
|
||||
|
||||
public async Task SaveUserData()
|
||||
{
|
||||
await AccountManager.SaveUserDataFile(
|
||||
CalcPaths.RippleSpaceAccountData(PlaneManager.SourceDirectory)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -310,7 +310,7 @@ namespace Nibriboard.RippleSpace
|
|||
foreach(KeyValuePair<ChunkReference, Chunk> loadedChunkItem in loadedChunkspace)
|
||||
{
|
||||
// Figure out where to put the chunk and create the relevant directories
|
||||
string chunkDestinationFilename = CalcPaths.ChunkFilepath(StorageDirectory, loadedChunkItem.Key);
|
||||
string chunkDestinationFilename = CalcPaths.ChunkFilePath(StorageDirectory, loadedChunkItem.Key);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(chunkDestinationFilename));
|
||||
|
||||
// Ask the chunk to save itself, but only if it isn't empty
|
||||
|
@ -336,7 +336,7 @@ namespace Nibriboard.RippleSpace
|
|||
long totalSize = 0;
|
||||
foreach (KeyValuePair<ChunkReference, Chunk> loadedChunkItem in loadedChunkspace)
|
||||
{
|
||||
string destFilename = CalcPaths.ChunkFilepath(StorageDirectory, loadedChunkItem.Key);
|
||||
string destFilename = CalcPaths.ChunkFilePath(StorageDirectory, loadedChunkItem.Key);
|
||||
if (!File.Exists(destFilename)) // Don't assume that the file exists - it might be an empty chunk
|
||||
continue;
|
||||
totalSize += (new FileInfo(destFilename)).Length;
|
||||
|
|
|
@ -28,5 +28,25 @@ namespace Nibriboard.Userspace
|
|||
{
|
||||
return Permissions.Contains(permission) || SubRoles.Any((RbacRole obj) => obj.HasPermission(permission));
|
||||
}
|
||||
|
||||
public bool HasRole(RbacRole targetRole)
|
||||
{
|
||||
if (Name == targetRole.Name)
|
||||
return true;
|
||||
return SubRoles.Any((RbacRole subRole) => subRole.HasRole(targetRole));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
List<string> subItems = new List<string>();
|
||||
subItems.AddRange(SubRoles.Select((RbacRole subRole) => $"[r] {subRole.Name}"));
|
||||
subItems.AddRange(Permissions.Select((RbacPermission subPermission) => $"[p] {subPermission.Name}"));
|
||||
|
||||
return string.Format(
|
||||
"{0}: {1}",
|
||||
Name,
|
||||
string.Join(", ", subItems)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Nibriboard.Userspace
|
|||
public string HashedPassword { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<RbacRole> Roles { get; set; }
|
||||
public List<RbacRole> Roles { get; set; } = new List<RbacRole>();
|
||||
|
||||
private List<string> rolesText = null;
|
||||
public List<string> RolesText {
|
||||
|
@ -48,7 +48,7 @@ namespace Nibriboard.Userspace
|
|||
return new List<string>(Roles.Select((RbacRole role) => role.Name));
|
||||
}
|
||||
set {
|
||||
|
||||
rolesText = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,10 +85,15 @@ namespace Nibriboard.Userspace
|
|||
return Roles.Any((RbacRole role) => role.HasPermission(permission));
|
||||
}
|
||||
|
||||
public bool HasRole(RbacRole targetRole)
|
||||
{
|
||||
return Roles.Any((RbacRole role) => role.HasRole(targetRole));
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
internal void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
Roles = new List<RbacRole>(userManager.ResolveRoles(RolesText));
|
||||
Roles = new List<RbacRole>(userManager.ResolveRoles(rolesText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,17 @@ namespace Nibriboard.Userspace
|
|||
{
|
||||
public class UserManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of users that this <see cref="UserManager" /> is managing.
|
||||
/// </summary>
|
||||
public List<User> Users { get; private set; } = new List<User>();
|
||||
/// <summary>
|
||||
/// A list of the permissions that this <see cref="UserManager" /> is aware of.
|
||||
/// </summary>
|
||||
public List<RbacPermission> Permissions { get; private set; } = new List<RbacPermission>();
|
||||
/// <summary>
|
||||
/// A list of the roles that this <see cref="UserManager" /> is aware of.
|
||||
/// </summary>
|
||||
public List<RbacRole> Roles { get; private set; } = new List<RbacRole>();
|
||||
|
||||
public UserManager()
|
||||
|
@ -45,23 +54,75 @@ namespace Nibriboard.Userspace
|
|||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the user data stored in the specified file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename to load the user data from.</param>
|
||||
public async Task LoadUserDataFile(string filename)
|
||||
{
|
||||
StreamReader sourceStream = new StreamReader(filename);
|
||||
await LoadUserData(sourceStream);
|
||||
}
|
||||
/// <summary>
|
||||
/// Loads the user data from the specified stream.
|
||||
/// </summary>
|
||||
/// <param name="userDataStream">The stream to load the user data from.</param>
|
||||
public async Task LoadUserData(StreamReader userDataStream)
|
||||
{
|
||||
LoadUserData(await userDataStream.ReadToEndAsync());
|
||||
}
|
||||
/// <summary>
|
||||
/// Loads the user data from the specified JSON string.
|
||||
/// </summary>
|
||||
/// <param name="userData">The JSON-serialised user data to load.</param>
|
||||
public void LoadUserData(string userData)
|
||||
{
|
||||
Users = JsonConvert.DeserializeObject<List<User>>(userData, new UserCreationConverter(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the user data to the specified file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename to save the user data to.</param>
|
||||
public async Task SaveUserDataFile(string filename)
|
||||
{
|
||||
StreamWriter destination = new StreamWriter(filename);
|
||||
await SaveUserData(destination);
|
||||
destination.Close();
|
||||
}
|
||||
/// <summary>
|
||||
/// Saves the user data to specified destination.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination to save to.</param>
|
||||
public async Task SaveUserData(StreamWriter destination)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(Users);
|
||||
await destination.WriteLineAsync(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a permission name to it's associated <see cref="RbacPermission" /> object.
|
||||
/// </summary>
|
||||
/// <param name="permissionName">The permission name to resolve.</param>
|
||||
/// <returns>The resolved permission object.</returns>
|
||||
public RbacPermission ResolvePermission(string permissionName)
|
||||
{
|
||||
return Permissions.Find((RbacPermission permission) => permission.Name == permissionName);
|
||||
}
|
||||
/// <summary>
|
||||
/// Resolves a role name to it's associated <see cref="RbacRole" /> object.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The role name to resolve.</param>
|
||||
/// <returns>The resolved role object.</returns>
|
||||
public RbacRole ResolveRole(string roleName)
|
||||
{
|
||||
return Roles.Find((RbacRole role) => role.Name == roleName);
|
||||
}
|
||||
/// <summary>
|
||||
/// Resolves a list of role names to their associated <see cref="RbacRole" /> objects.
|
||||
/// </summary>
|
||||
/// <param name="roleNames">The role names to resolve.</param>
|
||||
/// <returns>The resolved role objects.</returns>
|
||||
public IEnumerable<RbacRole> ResolveRoles(IEnumerable<string> roleNames)
|
||||
{
|
||||
foreach (RbacRole role in Roles)
|
||||
|
@ -70,5 +131,43 @@ namespace Nibriboard.Userspace
|
|||
yield return role;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Works out whether a user exists with the specified username.
|
||||
/// </summary>
|
||||
/// <param name="username">The target username to search for.</param>
|
||||
/// <returns>Whether a user exists with the specified username or not.</returns>
|
||||
public bool UserExists(string username)
|
||||
{
|
||||
return Users.Any((User user) => user.Username == username);
|
||||
}
|
||||
/// <summary>
|
||||
/// Finds the user with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to search for.</param>
|
||||
/// <returns>The user with the specified name.</returns>
|
||||
public User GetByName(string username)
|
||||
{
|
||||
return Users.Find((User user) => user.Username == username);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new user with the specified username and password, and adds them to the system.
|
||||
/// </summary>
|
||||
/// <param name="username">The username for the new user.</param>
|
||||
/// <param name="password">The new user's password.</param>
|
||||
public void AddUser(string username, string password)
|
||||
{
|
||||
if (UserExists(username))
|
||||
throw new Exception($"Error: A user with the name {username} already exists, so it can't be created.");
|
||||
|
||||
User newUser = new User(this) {
|
||||
Username = username,
|
||||
CreationTime = DateTime.Now
|
||||
};
|
||||
newUser.SetPassword(password);
|
||||
|
||||
Users.Add(newUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,13 @@ namespace Nibriboard.Utilities
|
|||
return Path.Combine(planeDirectory, "plane-index.json");
|
||||
}
|
||||
|
||||
public static string RippleSpaceAccountData(string rippleSpaceRoot)
|
||||
{
|
||||
return Path.Combine(rippleSpaceRoot, "user-data.json");
|
||||
}
|
||||
|
||||
public static string ChunkFilepath(string planeStorageDirectory, ChunkReference chunkRef)
|
||||
|
||||
public static string ChunkFilePath(string planeStorageDirectory, ChunkReference chunkRef)
|
||||
{
|
||||
return Path.Combine(planeStorageDirectory, chunkRef.AsFilepath());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue