Added permission groups. Cleaned security ui. Added some permission stuff

This commit is contained in:
Marcel Baumgartner 2023-07-17 22:16:39 +02:00
parent 0015001d7c
commit d3b55d155b
10 changed files with 430 additions and 245 deletions

View file

@ -22,6 +22,10 @@ public class PermissionStorage
{
return BitHelper.ReadBit(Data, permission.Index);
}
catch (ArgumentOutOfRangeException)
{
return false;
}
catch (Exception e)
{
Logger.Verbose("Error reading permissions. (Can be intentional)");
@ -37,4 +41,15 @@ public class PermissionStorage
Data = BitHelper.WriteBit(Data, permission.Index, value);
}
}
public bool HasAnyPermissions()
{
foreach (var permission in Permissions.GetAllPermissions())
{
if (this[permission])
return true;
}
return false;
}
}

View file

@ -261,10 +261,10 @@ public static class Permissions
Description = "Manage mail settings in the admin area"
};
public static Permission AdminSysMalware = new()
public static Permission AdminSecurityMalware = new()
{
Index = 39,
Name = "Admin system Malware",
Name = "Admin security Malware",
Description = "Manage malware settings in the admin area"
};
@ -275,11 +275,11 @@ public static class Permissions
Description = "View system resources in the admin area"
};
public static Permission AdminSysSecurity = new()
public static Permission AdminSecurity = new()
{
Index = 41,
Name = "Admin system Security",
Description = "Manage security settings in the admin area"
Name = "Admin Security",
Description = "View security logs in the admin area"
};
public static Permission AdminSysSentry = new()
@ -379,6 +379,20 @@ public static class Permissions
Name = "Admin Webspaces Server New",
Description = "Create a new webspace server in the admin area"
};
public static Permission AdminSecurityIpBans = new()
{
Index = 56,
Name = "Admin security ip bans",
Description = "Manage ip bans in the admin area"
};
public static Permission AdminSecurityPermissionGroups = new()
{
Index = 57,
Name = "Admin security permission groups",
Description = "View, add and delete permission groups"
};
public static Permission? FromString(string name)
{

View file

@ -242,11 +242,22 @@ public class IdentityService
Permissions.IsReadyOnly = true;
return;
}
Permissions = new PermissionStorage(BitHelper.OverwriteByteArrays(
UserPermissions.Data,
GroupPermissions.Data),
true
);
Permissions = new(Array.Empty<byte>());
foreach (var permission in Perms.Permissions.GetAllPermissions())
{
Permissions[permission] = GroupPermissions[permission];
}
foreach (var permission in Perms.Permissions.GetAllPermissions())
{
if (UserPermissions[permission])
{
Permissions[permission] = true;
}
}
Permissions.IsReadyOnly = true;
}
}

View file

@ -0,0 +1,32 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/security">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/security/malware">
<TL>Malware</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/security/ipbans">
<TL>Ip bans</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/security/permissiongroups">
<TL>Permission groups</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View file

@ -69,7 +69,7 @@ else
</a>
</div>
if (User.Admin)
if (IdentityService.Permissions.HasAnyPermissions())
{
<div class="menu-item pt-5">
<div class="menu-content">
@ -92,6 +92,14 @@ else
<span class="menu-title"><TL>System</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/security">
<span class="menu-icon">
<i class="bx bx-shield"></i>
</span>
<span class="menu-title"><TL>Security</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/servers">
<span class="menu-icon">

View file

@ -0,0 +1,7 @@
@page "/admin/security"
@using Moonlight.Shared.Components.Navigations
@attribute [PermissionRequired(nameof(Permissions.AdminSecurity))]
<AdminSecurityNavigation Index="0" />

View file

@ -1,4 +1,4 @@
@page "/admin/system/security"
@page "/admin/security/ipbans"
@using Moonlight.Shared.Components.Navigations
@using BlazorTable
@ -13,9 +13,9 @@
@inject EventSystem Event
@inject ToastService ToastService
@attribute [PermissionRequired(nameof(Permissions.AdminSysSecurity))]
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityIpBans))]
<AdminSystemNavigation Index="3"/>
<AdminSecurityNavigation Index="2"/>
<div class="card mb-5">
<div class="card-header">
@ -41,31 +41,33 @@
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<Table TableItem="IpBan" Items="IpBans" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="IpBan" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
<Column TableItem="IpBan" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<DeleteButton Confirm="true" OnClick="() => DeleteIpBan(context)"></DeleteButton>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
<div class="table-responsive">
<Table TableItem="IpBan" Items="AllIpBans" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="IpBan" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
<Column TableItem="IpBan" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<DeleteButton Confirm="true" OnClick="() => DeleteIpBan(context)"></DeleteButton>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
@code
{
private IpBan[] IpBans;
private IpBan[] AllIpBans;
private string Ip;
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
IpBans = IpBanRepository.Get().ToArray();
AllIpBans = IpBanRepository.Get().ToArray();
return Task.CompletedTask;
}

View file

@ -1,4 +1,4 @@
@page "/admin/system/malware"
@page "/admin/security/malware"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Background
@ -14,9 +14,9 @@
@implements IDisposable
@attribute [PermissionRequired(nameof(Permissions.AdminSysMalware))]
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityMalware))]
<AdminSystemNavigation Index="2"/>
<AdminSecurityNavigation Index="1"/>
<div class="row">
<div class="col-12 col-lg-6">

View file

@ -0,0 +1,132 @@
@page "/admin/security/permissiongroups"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@inject SmartTranslateService SmartTranslateService
@inject Repository<PermissionGroup> PermissionGroupRepository
@inject SessionServerService SessionServerService
@inject Repository<User> UserRepository
@inject AlertService AlertService
@inject ToastService ToastService
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityPermissionGroups))]
<AdminSecurityNavigation Index="3"/>
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Permission groups</TL>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("New"))"
CssClasses="btn-sm btn-success"
OnClick="NewGroupPermission">
</WButton>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive">
<Table TableItem="PermissionGroup" Items="AllPermissionGroups" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="PermissionGroup" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Filterable="true" Sortable="false"/>
<Column TableItem="PermissionGroup" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<WButton Text="@(SmartTranslateService.Translate("Edit permissions"))"
CssClasses="btn-primary me-2"
OnClick="() => EditPermissions(context)">
</WButton>
<DeleteButton Confirm="true" OnClick="() => DeletePermissionGroup(context)"></DeleteButton>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
<PermissionEditor @ref="PermissionEditor" OnSave="OnSave"/>
@code
{
private PermissionGroup[] AllPermissionGroups;
private PermissionGroup CurrentPermissionGroup;
private LazyLoader LazyLoader;
private PermissionEditor PermissionEditor;
private Task Load(LazyLoader arg)
{
AllPermissionGroups = PermissionGroupRepository
.Get()
.ToArray();
return Task.CompletedTask;
}
private async Task EditPermissions(PermissionGroup group)
{
CurrentPermissionGroup = group;
PermissionEditor.InitialData = CurrentPermissionGroup.Permissions;
await PermissionEditor.Launch();
}
private async Task DeletePermissionGroup(PermissionGroup group)
{
PermissionGroupRepository.Delete(group);
await LazyLoader.Reload();
}
private async Task OnSave(byte[] data)
{
CurrentPermissionGroup.Permissions = data;
PermissionGroupRepository.Update(CurrentPermissionGroup);
await ToastService.Success("Successfully modified permissions");
var usersWithTheGroup = UserRepository
.Get()
.Include(x => x.PermissionGroup)
.Where(x => x.PermissionGroup != null)
.Where(x => x.PermissionGroup!.Id == CurrentPermissionGroup.Id)
.ToArray();
foreach (var user in usersWithTheGroup)
{
await SessionServerService.ReloadUserSessions(user);
}
}
private async Task NewGroupPermission()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Enter the name for the new group"),
"",
""
);
if(string.IsNullOrEmpty(name))
return;
var group = new PermissionGroup()
{
Name = name,
Permissions = Array.Empty<byte>()
};
PermissionGroupRepository.Add(group);
await LazyLoader.Reload();
}
}

View file

@ -1,245 +1,201 @@
@page "/admin/users/view/{Id:int}"
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Files
@inject UserRepository UserRepository
@inject ServerRepository ServerRepository
@inject DomainRepository DomainRepository
@inject Repository<User> UserRepository
@inject Repository<Server> ServerRepository
@inject Repository<Domain> DomainRepository
@inject Repository<WebSpace> WebSpaceRepository
@inject ResourceService ResourceService
@attribute [PermissionRequired(nameof(Permissions.AdminUserView))]
<LazyLoader Load="Load">
@if (User == null)
{
<div class="alert alert-danger">
<TL>No user with this id found</TL>
</div>
}
else
{
<div class="row">
<div class="col-md-4">
<div class="card card-body mb-5">
<div class="d-flex flex-column align-items-center text-center">
<img src="/api/moonlight/avatar/@(User.Id)" class="rounded-circle" alt="Profile picture" width="150">
@if (User == null)
{
<div class="alert alert-danger">
<TL>No user with this id found</TL>
</div>
}
else
{
<div class="d-flex flex-column flex-lg-row">
<div class="flex-column flex-lg-row-auto w-lg-250px w-xl-350px mb-10">
<div class="card mb-5 mb-xl-8">
<div class="card-body">
<div class="d-flex flex-center flex-column py-5">
<div class="symbol symbol-100px symbol-circle mb-7">
<img src="@(ResourceService.Avatar(User))" alt="Avatar">
</div>
<span class="fs-3 text-gray-800 fw-bold mb-3">
@(User.FirstName) @(User.LastName)
</span>
@if (User.Admin)
{
<div class="mb-5">
<div class="badge badge-lg badge-light-primary d-inline">
<TL>Admin</TL>
</div>
</div>
}
</div>
<div>
<div class="pb-5 fs-6">
<div class="fw-bold mt-5">
<TL>Account ID</TL>
</div>
<div class="text-gray-600">@(User.Id)</div>
<div class="fw-bold mt-5">Email</div>
<div class="text-gray-600">
@(User.Email)
</div>
<div class="fw-bold mt-5">
<TL>Address</TL>
</div>
<div class="text-gray-600">@(User.Address), <br>@(User.City)<br>@(User.State)<br>@(User.Country)</div>
<div class="fw-bold mt-5">
<TL>Status</TL>
</div>
<div class="text-gray-600">
@(User.Status)
</div>
<div class="fw-bold mt-5">
<TL>TOTP</TL>
</div>
<div class="text-gray-600">
@(User.TotpEnabled)
</div>
<div class="fw-bold mt-5">
<TL>Discord ID</TL>
</div>
<div class="text-gray-600">
@(User.DiscordId)
</div>
<div class="fw-bold mt-5">
<TL>Last Login IP</TL>
</div>
<div class="text-gray-600">
@(User.LastIp) <TL>at</TL> @(Formatter.FormatDate(User.LastVisitedAt))
</div>
<div class="fw-bold mt-5">
<TL>Register IP</TL>
</div>
<div class="text-gray-600">
@(User.RegisterIp) <TL>at</TL> @(Formatter.FormatDate(User.CreatedAt))
</div>
</div>
</div>
</div>
<div class="card card-body mb-5">
<div class="btn-group">
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)">
</div>
</div>
<div class="flex-lg-row-fluid ms-lg-15">
<div class="card mb-3">
<div class="card-header border-0">
<span class="card-title">
<TL>Services</TL>
</span>
<div class="card-toolbar">
<a href="/admin/users/edit/@(User.Id)" class="btn btn-primary">
<TL>Edit</TL>
</a>
<a class="btn btn-secondary" href="/admin/users">
<TL>Back to list</TL>
</a>
<a class="btn btn-primary" href="/admin/support/view/@(User.Id)">
<TL>Open support</TL>
</a>
</div>
</div>
<div class="card card-xl-stretch mb-5">
<div class="card-header border-0">
<h3 class="card-title fw-bold text-dark">
</div>
<div class="accordion mb-3" id="serversList">
<div class="accordion-item">
<h2 class="accordion-header" id="serversList-header">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serversList-body" aria-expanded="false" aria-controls="serversList-body">
<TL>Servers</TL>
</h3>
</div>
<div class="card-body pt-2">
@foreach (var server in Servers)
{
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
</div>
</div>
if (server != Servers.Last())
</button>
</h2>
<div id="serversList-body" class="accordion-collapse collapse" aria-labelledby="serversList-header" data-bs-parent="#serversList">
<div class="accordion-body">
@foreach (var server in Servers)
{
<div class="separator my-4"></div>
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
</div>
</div>
if (server != Servers.Last())
{
<div class="separator my-4"></div>
}
}
}
</div>
</div>
</div>
<div class="card card-xl-stretch">
<div class="card-header border-0">
<h3 class="card-title fw-bold text-dark">
</div>
<div class="accordion mb-3" id="domainsList">
<div class="accordion-item">
<h2 class="accordion-header" id="domainsList-header">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#domainsList-body" aria-expanded="false" aria-controls="domainsList-body">
<TL>Domains</TL>
</h3>
</div>
<div class="card-body pt-2">
@foreach (var domain in Domains)
{
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
</div>
</div>
if (domain != Domains.Last())
</button>
</h2>
<div id="domainsList-body" class="accordion-collapse collapse" aria-labelledby="domainsList-header" data-bs-parent="#domainsList">
<div class="accordion-body">
@foreach (var domain in Domains)
{
<div class="separator my-4"></div>
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
</div>
</div>
if (domain != Domains.Last())
{
<div class="separator my-4"></div>
}
}
}
</div>
</div>
</div>
<div class="col-md-8">
<div class="card mb-3">
<div class="card-body fs-6">
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>First name</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.FirstName)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Last name</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.LastName)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Email</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.Email)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Address</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.Address)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>City</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.City)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>State</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.State)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Country</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.Country)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Admin</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">
@if (User.Admin)
{
<span>✅</span>
}
else
{
<span>❌</span>
}
</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Status</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.Status)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Totp</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.TotpEnabled)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Discord</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.DiscordId)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Subscription</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">
</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Created at</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(Formatter.FormatDate(User.CreatedAt))</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Register ip</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.RegisterIp)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Last ip</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.LastIp)</span>
</div>
</div>
</div>
</div>
</div>
<div class="accordion mb-3" id="webspacesList">
<div class="accordion-item">
<h2 class="accordion-header" id="webspacesList-header">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#webspacesList-body" aria-expanded="false" aria-controls="webspacesList-body">
<TL>Webspaces</TL>
</button>
</h2>
<div id="webspacesList-body" class="accordion-collapse collapse" aria-labelledby="webspacesList-header" data-bs-parent="#webspacesList">
<div class="accordion-body">
@foreach (var webSpace in WebSpaces)
{
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<a href="/webspace/@(webSpace.Id)" class="fs-6">@(webSpace.Domain) - @(webSpace.CloudPanel.Name)</a>
</div>
</div>
if (webSpace != WebSpaces.Last())
{
<div class="separator my-4"></div>
}
}
</div>
</div>
</div>
</div>
}
</LazyLoader>
</div>
</div>
}
</LazyLoader>
@code
{
@ -249,6 +205,7 @@
private User? User;
private Server[] Servers;
private Domain[] Domains;
private WebSpace[] WebSpaces;
private Task Load(LazyLoader arg)
{
@ -269,6 +226,13 @@
.Include(x => x.Owner)
.Where(x => x.Owner.Id == User.Id)
.ToArray();
WebSpaces = WebSpaceRepository
.Get()
.Include(x => x.CloudPanel)
.Include(x => x.Owner)
.Where(x => x.Owner.Id == User.Id)
.ToArray();
}
return Task.CompletedTask;