Skip to content

Commit

Permalink
Hide PII from admins who lack that permission (#70)
Browse files Browse the repository at this point in the history
* Sync AdminFlags.cs

* PII constant

* Hide PII from Players pages

* Don't allow searching for PII when you lack the role

* 1984 PII from connection logs and ban hits

* Nuke another check that might leak PII

* I forgor

* Don't allow filling bans from last connection

* Replace use latest buttons with a checkbox

* Do the thing
  • Loading branch information
NullWanderer authored Jun 21, 2024
1 parent f7c948a commit 92aceb8
Show file tree
Hide file tree
Showing 17 changed files with 161 additions and 66 deletions.
68 changes: 67 additions & 1 deletion SS14.Admin/Admins/AdminFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum AdminFlags : uint

/// <summary>
/// !!FUN!!
/// This is stuff that trial administrators shouldn't quite have access to yet, e.g. for running events.
/// </summary>
Fun = 1 << 3,

Expand Down Expand Up @@ -58,9 +59,74 @@ public enum AdminFlags : uint
/// </summary>
//Piss = 1 << 9,

/// <summary>
/// Lets you view admin logs.
/// </summary>
Logs = 1 << 9,

/// <summary>
/// Lets you modify the round (forcemap, loadgamemap, etc)
/// </summary>
Round = 1 << 10,

/// <summary>
/// Lets you use BQL queries.
/// </summary>
Query = 1 << 11,

/// <summary>
/// Lets you use the admin help system.
/// </summary>
Adminhelp = 1 << 12,

/// <summary>
/// Lets you view admin notes.
/// </summary>
ViewNotes = 1 << 13,

/// <summary>
/// Lets you create, edit and delete admin notes.
/// </summary>
EditNotes = 1 << 14,

/// <summary>
/// Lets you Massban, on SS14.Admin
/// </summary>
MassBan = 1 << 15,

/// <summary>
/// Allows you to remain hidden from adminwho except to other admins with this flag.
/// </summary>
Stealth = 1 << 16,

///<summary>
/// Allows you to use Admin chat
///</summary>
Adminchat = 1 << 17,

///<summary>
/// Permits the visibility of Pii in game and on SS14 Admin
///</summary>
Pii = 1 << 18,

/// <summary>
/// Lets you take moderator actions on the game server.
/// </summary>
Moderator = 1 << 19,

/// <summary>
/// Lets you check currently online admins.
/// </summary>
AdminWho = 1 << 20,

/// <summary>
/// Lets you set the color of your OOC name.
/// </summary>
NameColor = 1 << 21,

/// <summary>
/// Dangerous host permissions like scsi.
/// </summary>
Host = 1u << 31,
}
}
}
2 changes: 2 additions & 0 deletions SS14.Admin/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
public static class Constants
{
public const int HwidLength = 32;

public const string PIIRole = "PII";
}
33 changes: 17 additions & 16 deletions SS14.Admin/Helpers/SearchHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq.Expressions;
using System.Net;
using System.Security.Claims;
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using WhitelistJoin = SS14.Admin.Helpers.WhitelistHelper.WhitelistJoin;
Expand All @@ -8,7 +9,7 @@ namespace SS14.Admin.Helpers;

public static class SearchHelper
{
public static IQueryable<ConnectionLog> SearchConnectionLog(IQueryable<ConnectionLog> query, string? search)
public static IQueryable<ConnectionLog> SearchConnectionLog(IQueryable<ConnectionLog> query, string? search, ClaimsPrincipal user)
{
if (string.IsNullOrEmpty(search))
return query;
Expand All @@ -21,21 +22,21 @@ public static IQueryable<ConnectionLog> SearchConnectionLog(IQueryable<Connectio
if (Guid.TryParse(search, out var guid))
CombineSearch(ref expr, u => u.UserId == guid);

if (IPHelper.TryParseCidr(search, out var cidr))
if (user.IsInRole(Constants.PIIRole) && IPHelper.TryParseCidr(search, out var cidr))
CombineSearch(ref expr, u => EF.Functions.Contains(cidr, u.Address));

if (IPAddress.TryParse(search, out var ip))
if (user.IsInRole(Constants.PIIRole) && IPAddress.TryParse(search, out var ip))
CombineSearch(ref expr, u => u.Address.Equals(ip));

var hwid = new byte[Constants.HwidLength];
if (Convert.TryFromBase64String(search, hwid, out var len) && len == Constants.HwidLength)
if (user.IsInRole(Constants.PIIRole) && Convert.TryFromBase64String(search, hwid, out var len) && len == Constants.HwidLength)
CombineSearch(ref expr, u => u.HWId == hwid);

return query.Where(expr);
}

private static Expression<Func<BanHelper.BanJoin<TBan, TUnban>, bool>> MakeCommonBanSearchExpression<TBan, TUnban>(
string search)
string search, ClaimsPrincipal user)
where TBan : IBanCommon<TUnban>
where TUnban : IUnbanCommon
{
Expand All @@ -48,51 +49,51 @@ public static IQueryable<ConnectionLog> SearchConnectionLog(IQueryable<Connectio
if (Guid.TryParse(search, out var guid))
CombineSearch(ref expr, b => b.Ban.PlayerUserId == guid);

if (IPHelper.TryParseCidr(search, out var cidr))
if (user.IsInRole(Constants.PIIRole) && IPHelper.TryParseCidr(search, out var cidr))
CombineSearch(ref expr, b => EF.Functions.ContainsOrEqual(cidr, b.Ban.Address!.Value));

if (IPAddress.TryParse(search, out var ip))
if (user.IsInRole(Constants.PIIRole) && IPAddress.TryParse(search, out var ip))
CombineSearch(ref expr, u => EF.Functions.ContainsOrEqual(u.Ban.Address!.Value, ip));

var hwid = new byte[Constants.HwidLength];
if (Convert.TryFromBase64String(search, hwid, out var len) && len == Constants.HwidLength)
if (user.IsInRole(Constants.PIIRole) && Convert.TryFromBase64String(search, hwid, out var len) && len == Constants.HwidLength)
CombineSearch(ref expr, u => u.Ban.HWId == hwid);

return expr;
}

public static IQueryable<BanHelper.BanJoin<ServerBan, ServerUnban>> SearchServerBans(
IQueryable<BanHelper.BanJoin<ServerBan, ServerUnban>> query,
string? search)
string? search, ClaimsPrincipal user)
{
if (string.IsNullOrEmpty(search))
return query;

search = search.Trim();

var expr = MakeCommonBanSearchExpression<ServerBan, ServerUnban>(search);
var expr = MakeCommonBanSearchExpression<ServerBan, ServerUnban>(search, user);

return query.Where(expr);
}

public static IQueryable<BanHelper.BanJoin<ServerRoleBan, ServerRoleUnban>> SearchRoleBans(
IQueryable<BanHelper.BanJoin<ServerRoleBan, ServerRoleUnban>> query,
string? search)
string? search, ClaimsPrincipal user)
{
if (string.IsNullOrEmpty(search))
return query;

search = search.Trim();

var expr = MakeCommonBanSearchExpression<ServerRoleBan, ServerRoleUnban>(search);
var expr = MakeCommonBanSearchExpression<ServerRoleBan, ServerRoleUnban>(search, user);

// Match role name exactly.
CombineSearch(ref expr, u => u.Ban.RoleId == search);

return query.Where(expr);
}

public static IQueryable<Player> SearchPlayers(IQueryable<Player> query, string? search)
public static IQueryable<Player> SearchPlayers(IQueryable<Player> query, string? search, ClaimsPrincipal user)
{
if (string.IsNullOrEmpty(search))
return query;
Expand All @@ -105,14 +106,14 @@ public static IQueryable<Player> SearchPlayers(IQueryable<Player> query, string?
if (Guid.TryParse(search, out var guid))
CombineSearch(ref expr, u => u.UserId == guid);

if (IPHelper.TryParseCidr(search, out var cidr))
if (user.IsInRole(Constants.PIIRole) && IPHelper.TryParseCidr(search, out var cidr))
CombineSearch(ref expr, u => EF.Functions.Contains(cidr, u.LastSeenAddress));

if (IPAddress.TryParse(search, out var ip))
if (user.IsInRole(Constants.PIIRole) && IPAddress.TryParse(search, out var ip))
CombineSearch(ref expr, u => u.LastSeenAddress.Equals(ip));

var hwid = new byte[Constants.HwidLength];
if (Convert.TryFromBase64String(search, hwid, out var len) && len == Constants.HwidLength)
if (user.IsInRole(Constants.PIIRole) && Convert.TryFromBase64String(search, hwid, out var len) && len == Constants.HwidLength)
CombineSearch(ref expr, u => u.LastSeenHWId == hwid);

return query.Where(expr);
Expand Down
10 changes: 6 additions & 4 deletions SS14.Admin/Pages/Bans/Create.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
<div class="col-sm">
<input asp-for="Input.IP" value="@Model.Input.IP" class="form-control">
</div>
<div class="col-sm-auto">
<input asp-page-handler="Fill" type="submit" value="Find last Info" class="btn btn-secondary btn-block">
<div class="col-sm-auto align-items-center d-flex">
<input asp-for="Input.UseLatestIp" class="form-check-input" style="margin-right: 5px;">
<label class="form-check-label">Use latest</label>
</div>
</div>
</div>
Expand All @@ -34,8 +35,9 @@
<div class="col-sm">
<input asp-for="Input.HWid" value="@Model.Input.HWid" class="form-control">
</div>
<div class="col-sm-auto">
<input asp-page-handler="Fill" type="submit" value="Find last Info" class="btn btn-secondary btn-block">
<div class="col-sm-auto align-items-center d-flex">
<input asp-for="Input.UseLatestHwid" class="form-check-input" style="margin-right: 5px;">
<label class="form-check-label">Use latest</label>
</div>
</div>
</div>
Expand Down
43 changes: 24 additions & 19 deletions SS14.Admin/Pages/Bans/Create.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,43 @@ public sealed class InputModel
public string? NameOrUid { get; set; }
public string? IP { get; set; }
public string? HWid { get; set; }
public bool UseLatestIp {get; set; }
public bool UseLatestHwid { get; set; }
public int LengthMinutes { get; set; }
[Required] public string Reason { get; set; } = "";
}

public async Task OnPostFillAsync()
public async Task<IActionResult> OnPostCreateAsync()
{
if (string.IsNullOrWhiteSpace(Input.NameOrUid))
{
TempData.SetStatusError("Must provide name/UID.");
return;
}
var ban = new ServerBan();

var ipAddr = Input.IP;
var hwid = Input.HWid;

var lastInfo = await _banHelper.GetLastPlayerInfo(Input.NameOrUid);
if (lastInfo == null)
if (Input.UseLatestHwid || Input.UseLatestIp)
{
TempData.SetStatusError("Unable to find player");
return;
}
if (string.IsNullOrWhiteSpace(Input.NameOrUid))
{
TempData.SetStatusError("Must provide name/UID.");
return Page();
}

Input.IP = lastInfo.Value.address.ToString();
Input.HWid = lastInfo.Value.hwid is { } h ? Convert.ToBase64String(h) : null;
}
var lastInfo = await _banHelper.GetLastPlayerInfo(Input.NameOrUid);
if (lastInfo == null)
{
TempData.SetStatusError("Unable to find player");
return Page();
}

public async Task<IActionResult> OnPostCreateAsync()
{
var ban = new ServerBan();
ipAddr = Input.UseLatestIp ? lastInfo.Value.address.ToString() : Input.IP;
hwid = Input.UseLatestHwid ? (lastInfo.Value.hwid is { } h ? Convert.ToBase64String(h) : null) : Input.HWid;
}

var error = await _banHelper.FillBanCommon(
ban,
Input.NameOrUid,
Input.IP,
Input.HWid,
ipAddr,
hwid,
Input.LengthMinutes,
Input.Reason);

Expand Down
2 changes: 1 addition & 1 deletion SS14.Admin/Pages/Bans/Hits.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task<IActionResult> OnGetAsync(
.Where(bh => bh.BanId == banEntry.Ban.Id)
.Select(bh => bh.Connection);

logQuery = SearchHelper.SearchConnectionLog(logQuery, search);
logQuery = SearchHelper.SearchConnectionLog(logQuery, search, User);

SortState = await ConnectionsIndexModel.LoadSortConnectionsTableData(
Pagination,
Expand Down
2 changes: 1 addition & 1 deletion SS14.Admin/Pages/Bans/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public async Task OnGetAsync(
{
Pagination.Init(pageIndex, perPage, AllRouteData);

var bans = SearchHelper.SearchServerBans(_banHelper.CreateServerBanJoin(), search);
var bans = SearchHelper.SearchServerBans(_banHelper.CreateServerBanJoin(), search, User);

bans = show switch
{
Expand Down
2 changes: 1 addition & 1 deletion SS14.Admin/Pages/Connections/Hits.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<dd class="col-sm-10">@Model.Log.UserName</dd>
<dt class="col-sm-2">User ID:</dt>
<dd class="col-sm-10 font-monospace">@Model.Log.UserId</dd>
@if (User.IsInRole("ADMIN"))
@if (User.IsInRole(Constants.PIIRole))
{
<dt class="col-sm-2">IP:</dt>
<dd class="col-sm-10 font-monospace">@Model.Log.Address</dd>
Expand Down
2 changes: 1 addition & 1 deletion SS14.Admin/Pages/Connections/Hits.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task<IActionResult> OnGetAsync(

Pagination.Init(pageIndex, perPage, AllRouteData);

var banQuery = SearchHelper.SearchServerBans(_banHelper.CreateServerBanJoin(), search)
var banQuery = SearchHelper.SearchServerBans(_banHelper.CreateServerBanJoin(), search, User)
.Join(_dbContext.ServerBanHit, bj => bj.Ban.Id, bh => bh.BanId, (join, hit) => new
{
join, hit
Expand Down
2 changes: 1 addition & 1 deletion SS14.Admin/Pages/Connections/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public async Task OnGetAsync(
AllRouteData.Add("showSet", "true");

IQueryable<ConnectionLog> logQuery = _dbContext.ConnectionLog;
logQuery = SearchHelper.SearchConnectionLog(logQuery, search);
logQuery = SearchHelper.SearchConnectionLog(logQuery, search, User);

var acceptableDenies = new List<ConnectionDenyReason?>();
if (showAccepted)
Expand Down
12 changes: 10 additions & 2 deletions SS14.Admin/Pages/Players/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,16 @@
</td>
<td class="font-monospace small">@player.UserId</td>
<td>@player.LastSeenTime.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td class="font-monospace constrained-150">@player.LastSeenAddress</td>
<td class="font-monospace constrained-150">@BanHelper.FormatHwid(player.LastSeenHWId)</td>
@if (User.IsInRole(Constants.PIIRole))
{
<td class="font-monospace constrained-150">@player.LastSeenAddress</td>
<td class="font-monospace constrained-150">@BanHelper.FormatHwid(player.LastSeenHWId)</td>
}
else
{
<td class="font-monospace constrained-150">Hidden</td>
<td class="font-monospace constrained-150">Hidden</td>
}
<td>@player.FirstSeenTime.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td>
<a asp-page="/Players/Info" asp-route-userId="@player.UserId" class="btn btn-secondary">Player Info</a>
Expand Down
2 changes: 1 addition & 1 deletion SS14.Admin/Pages/Players/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async Task OnGetAsync(
AllRouteData.Add("search", CurrentFilter);

IQueryable<Player> userQuery = _dbContext.Player;
userQuery = SearchHelper.SearchPlayers(userQuery, search);
userQuery = SearchHelper.SearchPlayers(userQuery, search, User);

userQuery = SortState.ApplyToQuery(userQuery);

Expand Down
Loading

0 comments on commit 92aceb8

Please sign in to comment.