From ff1634d395bae928d336d9eb55389a6f23a8a3d4 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sun, 8 Dec 2024 19:34:21 +0300 Subject: [PATCH 01/21] Add player management and enhance hub connections Introduced player management APIs, including a new endpoint for fetching player lists, and defined necessary data transfer objects. Enhanced hub connections by refactoring the PlayersController and GameServerHub, simplifying connection handling and user session management. Additionally, fixed a typo in the Russian summary description for profile fetching. --- .../Player/AuthUserHistoryDto.cs | 12 ++++ .../Player/ExtendedPlayerReadDto.cs | 10 +++ .../Player/ServerJoinHistoryDto.cs | 9 +++ .../Profile/ProfileReadInfoDto.cs | 2 +- .../Core/Extensions/EndpointsExtensions.cs | 19 +++++- .../Core/Handlers/IPlayersHandler.cs | 6 ++ .../Core/Handlers/PlayersHandler.cs | 17 +++++ .../Core/Handlers/ProfileHandler.cs | 1 + .../Hubs/Controllers/PlayersController.cs | 41 ++++++++---- src/Gml.Web.Api/Core/Hubs/GameServerHub.cs | 65 +++++-------------- src/Gml.Web.Api/Core/Hubs/HubEvents.cs | 19 +++++- src/Gml.Web.Api/Core/Hubs/LauncherHub.cs | 2 +- .../Core/MappingProfiles/PlayerMapper.cs | 3 + src/Gml.Web.Api/Gml.Web.Api.csproj | 3 + 14 files changed, 143 insertions(+), 66 deletions(-) create mode 100644 src/Gml.Web.Api.Dto/Player/AuthUserHistoryDto.cs create mode 100644 src/Gml.Web.Api.Dto/Player/ExtendedPlayerReadDto.cs create mode 100644 src/Gml.Web.Api.Dto/Player/ServerJoinHistoryDto.cs create mode 100644 src/Gml.Web.Api/Core/Handlers/IPlayersHandler.cs create mode 100644 src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs diff --git a/src/Gml.Web.Api.Dto/Player/AuthUserHistoryDto.cs b/src/Gml.Web.Api.Dto/Player/AuthUserHistoryDto.cs new file mode 100644 index 0000000..1c19cc4 --- /dev/null +++ b/src/Gml.Web.Api.Dto/Player/AuthUserHistoryDto.cs @@ -0,0 +1,12 @@ +using System; + +namespace Gml.Web.Api.Dto.Player; + +public class AuthUserHistoryDto +{ + public DateTime Date { get; set; } + public string Device { get; set; } + public string? Address { get; set; } + public string Protocol { get; set; } + public string? Hwid { get; set; } +} diff --git a/src/Gml.Web.Api.Dto/Player/ExtendedPlayerReadDto.cs b/src/Gml.Web.Api.Dto/Player/ExtendedPlayerReadDto.cs new file mode 100644 index 0000000..4003739 --- /dev/null +++ b/src/Gml.Web.Api.Dto/Player/ExtendedPlayerReadDto.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Gml.Web.Api.Dto.Player; + +public class ExtendedPlayerReadDto : PlayerReadDto +{ + public bool IsBanned { get; set; } + public List AuthHistory { get; set; } = new(); + public List ServerJoinHistory { get; set; } = new(); +} diff --git a/src/Gml.Web.Api.Dto/Player/ServerJoinHistoryDto.cs b/src/Gml.Web.Api.Dto/Player/ServerJoinHistoryDto.cs new file mode 100644 index 0000000..f8239e5 --- /dev/null +++ b/src/Gml.Web.Api.Dto/Player/ServerJoinHistoryDto.cs @@ -0,0 +1,9 @@ +using System; + +namespace Gml.Web.Api.Dto.Player; + +public record ServerJoinHistoryDto +{ + public string ServerUuid { get; set; } + public DateTime Date { get; set; } +} diff --git a/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs b/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs index e9b5c0f..d895f58 100644 --- a/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs +++ b/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs @@ -15,7 +15,7 @@ public class ProfileReadInfoDto public string IconBase64 { get; set; } public string Description { get; set; } public string Arguments { get; set; } - public string IsEnabled { get; set; } + public bool IsEnabled { get; set; } public string JvmArguments { get; set; } public string GameArguments { get; set; } public bool HasUpdate { get; set; } diff --git a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs index 17eeb89..7bbd131 100644 --- a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs +++ b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs @@ -12,6 +12,7 @@ using Gml.Web.Api.Dto.Settings; using Gml.Web.Api.Dto.User; using GmlCore.Interfaces.Notifications; +using GmlCore.Interfaces.User; namespace Gml.Web.Api.Core.Extensions; @@ -511,7 +512,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) app.MapGet("/api/v1/profiles", ProfileHandler.GetProfiles) .WithOpenApi(generatedOperation => { - generatedOperation.Summary = "Получение списка профилилей"; + generatedOperation.Summary = "Получение списка профилей"; return generatedOperation; }) .WithDescription("Получение списка профиля") @@ -627,6 +628,22 @@ public static WebApplication RegisterEndpoints(this WebApplication app) #endregion + #region Players + + app.MapGet("/api/v1/players", PlayersHandler.GetPlayers) + .WithOpenApi(generatedOperation => + { + generatedOperation.Summary = "Получение списка игроков"; + return generatedOperation; + }) + .WithDescription("Получение списка игроков") + .WithName("Players list") + .WithTags("Players") + .Produces>>() + .Produces((int)HttpStatusCode.BadRequest); + + #endregion + #region Files app.MapGet("/api/v1/file/{fileHash}", FileHandler.GetFile) diff --git a/src/Gml.Web.Api/Core/Handlers/IPlayersHandler.cs b/src/Gml.Web.Api/Core/Handlers/IPlayersHandler.cs new file mode 100644 index 0000000..5513e59 --- /dev/null +++ b/src/Gml.Web.Api/Core/Handlers/IPlayersHandler.cs @@ -0,0 +1,6 @@ +namespace Gml.Web.Api.Core.Handlers; + +public interface IPlayersHandler +{ + +} diff --git a/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs b/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs new file mode 100644 index 0000000..ac6bc0e --- /dev/null +++ b/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs @@ -0,0 +1,17 @@ +using System.Net; +using AutoMapper; +using Gml.Web.Api.Dto.Messages; +using Gml.Web.Api.Dto.Player; +using GmlCore.Interfaces; + +namespace Gml.Web.Api.Core.Handlers; + +public class PlayersHandler : IPlayersHandler +{ + public static async Task GetPlayers(IGmlManager gmlManager, IMapper mapper, int? take, int? offset, string? findName) + { + var players = await gmlManager.Users.GetUsers(take ?? 20, offset ?? 0, findName ?? string.Empty); + + return Results.Ok(ResponseMessage.Create(mapper.Map>(players), "Список пользователей успешно получен", HttpStatusCode.OK)); + } +} diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index b1a2073..f9e2caf 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -346,6 +346,7 @@ public static async Task GetProfileDetails( var profileDto = mapper.Map(profileInfo); profileDto.Background = $"{context.Request.Scheme}://{context.Request.Host}/api/v1/file/{profile.BackgroundImageKey}"; + profileDto.IsEnabled = profile.IsEnabled; return Results.Ok(ResponseMessage.Create(profileDto, string.Empty, HttpStatusCode.OK)); } diff --git a/src/Gml.Web.Api/Core/Hubs/Controllers/PlayersController.cs b/src/Gml.Web.Api/Core/Hubs/Controllers/PlayersController.cs index cff6469..ac37076 100644 --- a/src/Gml.Web.Api/Core/Hubs/Controllers/PlayersController.cs +++ b/src/Gml.Web.Api/Core/Hubs/Controllers/PlayersController.cs @@ -15,15 +15,12 @@ namespace Gml.Web.Api.Core.Hubs.Controllers; public class PlayersController : ConcurrentDictionary { private readonly IGmlManager _gmlManager; - private readonly HubEvents _hubEvents; public ConcurrentDictionary Timers = new(); - public ConcurrentDictionary Schedulers = new(); - public ConcurrentDictionary Servers = new(); - public ConcurrentDictionary LauncherConnections = new(); + public ConcurrentDictionary GameServersConnections = new(); + public ConcurrentDictionary LauncherInfos = new(); - public PlayersController(IGmlManager gmlManager, HubEvents hubEvents) + public PlayersController(IGmlManager gmlManager) { - _hubEvents = hubEvents; _gmlManager = gmlManager; } @@ -33,7 +30,7 @@ public async Task AddLauncherConnection(string connectionId, ISingleClientProxy var userName = contextUser.FindFirstValue(JwtRegisteredClaimNames.Name); if (!string.IsNullOrEmpty(userName) && await _gmlManager.Users.GetUserByName(userName) is AuthUser user) { - LauncherConnections.TryAdd(connectionId, new UserLauncherInfo + LauncherInfos.TryAdd(connectionId, new UserLauncherInfo { User = user, ExpiredDate = DateTimeOffset.Now.AddSeconds(30), @@ -45,7 +42,7 @@ public async Task AddLauncherConnection(string connectionId, ISingleClientProxy var timer = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(5)) .Subscribe(_ => { - if (!LauncherConnections.TryGetValue(connectionId, out var userInfo) + if (!LauncherInfos.TryGetValue(connectionId, out var userInfo) || DateTimeOffset.Now > userInfo.ExpiredDate) { RemoveLauncherConnection(connectionId); @@ -64,7 +61,7 @@ public async Task AddLauncherConnection(string connectionId, ISingleClientProxy public void RemoveLauncherConnection(string connectionId) { - if (LauncherConnections.TryRemove(connectionId, out var user)) + if (LauncherInfos.TryRemove(connectionId, out var user)) { // Останавливаем таймер для данного лаунчера if (Timers.TryRemove(connectionId, out var timer)) @@ -74,15 +71,15 @@ public void RemoveLauncherConnection(string connectionId) } Debug.WriteLine($"{user.User.Name} | {user.User.Uuid} | Disconnected"); - _hubEvents.KickUser.OnNext(user.User.Name); + _ = OnKickUser(user.User.Name, "Потеряно соединение с сервером"); } } public void ConfirmLauncherHash(string connectionId, string hash) { - if (LauncherConnections.TryGetValue(connectionId, out var user)) + if (LauncherInfos.TryGetValue(connectionId, out var user)) { - Debug.WriteLine($"{user.User.Name} | {user.User.Uuid} | Session active"); + Debug.WriteLine($"{user.User.Name} | {user.User.Uuid} | Session active | hash: {hash}"); user.ExpiredDate = DateTimeOffset.Now.AddSeconds(30); } } @@ -90,7 +87,7 @@ public void ConfirmLauncherHash(string connectionId, string hash) public bool GetLauncherConnection(string userName, out UserLauncherInfo? launcherInfo) { var userConnection = - LauncherConnections.FirstOrDefault(c => c.Value.User.Name == userName); + LauncherInfos.FirstOrDefault(c => c.Value.User.Name == userName); if (userConnection.Key is null) { @@ -98,6 +95,22 @@ public bool GetLauncherConnection(string userName, out UserLauncherInfo? launche return false; } - return LauncherConnections.TryGetValue(userConnection.Key, out launcherInfo); + return LauncherInfos.TryGetValue(userConnection.Key, out launcherInfo); + } + + internal async Task OnKickUser(string userName, string message) + { + foreach (var caller in GameServersConnections.Values) + { + try + { + await caller.SendAsync("KickUser", userName, message); + Debug.WriteLine($"User Kicked: {userName}"); + } + catch (Exception e) + { + Debug.WriteLine($"Ошибка при отправке сообщения на удаление: {e}"); + } + } } } diff --git a/src/Gml.Web.Api/Core/Hubs/GameServerHub.cs b/src/Gml.Web.Api/Core/Hubs/GameServerHub.cs index 462d5f3..a513dd4 100644 --- a/src/Gml.Web.Api/Core/Hubs/GameServerHub.cs +++ b/src/Gml.Web.Api/Core/Hubs/GameServerHub.cs @@ -7,55 +7,40 @@ namespace Gml.Web.Api.Core.Hubs; -public class GameServerHub : BaseHub +public class GameServerHub( + IGmlManager gmlManager, + PlayersController playerController, + HubEvents hubEvents) + : BaseHub { - private readonly IGmlManager _gmlManager; - private readonly PlayersController _playerController; - private readonly HubEvents _hubEvents; - - public GameServerHub( - IGmlManager gmlManager, - PlayersController playerController, - HubEvents hubEvents) - { - _gmlManager = gmlManager; - _hubEvents = hubEvents; - _playerController = playerController; - - hubEvents.KickUser.Subscribe(async userName => await KickUser(userName)); - } - public override Task OnConnectedAsync() { - _playerController.Servers.TryAdd(Context.ConnectionId, Clients.Caller); + playerController.GameServersConnections.TryAdd(Context.ConnectionId, Clients.Caller); return base.OnConnectedAsync(); } public override Task OnDisconnectedAsync(Exception? exception) { - _playerController.Servers.TryRemove(Context.ConnectionId, out _); + playerController.GameServersConnections.TryRemove(Context.ConnectionId, out _); return base.OnDisconnectedAsync(exception); } - private async Task KickUser(string userName) - { - await KickUser(userName, "Не удалось идентифицировать пользователя. Перезапустите игру вместе с лаунчером!"); - } + public async Task OnJoin(string userName) { try { - if (!_playerController.GetLauncherConnection(userName, out var launcherInfo) || launcherInfo!.ExpiredDate < DateTimeOffset.Now) + if (!playerController.GetLauncherConnection(userName, out var launcherInfo) || launcherInfo!.ExpiredDate < DateTimeOffset.Now) { - await KickUser(userName); + hubEvents.KickUser.OnNext((userName, "Не удалось идентифицировать пользователя. Перезапустите игру вместе с лаунчером!")); return; } Debug.WriteLine($"OnJoin: {userName}; ExpiredTime: {launcherInfo.ExpiredDate - DateTimeOffset.Now:g}"); - var user = await _gmlManager.Users.GetUserByName(userName); + var user = await gmlManager.Users.GetUserByName(userName); if (user is null) { @@ -63,41 +48,25 @@ public async Task OnJoin(string userName) return; } - await _gmlManager.Users.StartSession(user); + await gmlManager.Users.StartSession(user); } catch (Exception e) { - await KickUser(userName, "Произошла ошибка при попытке подключения к серверу"); + hubEvents.KickUser.OnNext((userName, "Произошла ошибка при попытке подключения к серверу")); Console.WriteLine(e); } } - private async Task KickUser(string userName, string message) - { - foreach (var caller in _playerController.Servers.Values) - { - try - { - await caller.SendAsync("KickUser", userName, message); - Debug.WriteLine($"User Kicked: {userName}"); - } - catch (Exception e) - { - Debug.WriteLine($"Ошибка при отправке сообщения на удаление: {e}"); - } - } - } - public async Task OnLeft(string userName) { try { - if (!_playerController.GetLauncherConnection(userName, out var launcherInfo) || launcherInfo!.ExpiredDate < DateTimeOffset.Now) + if (!playerController.GetLauncherConnection(userName, out var launcherInfo) || launcherInfo!.ExpiredDate < DateTimeOffset.Now) { return; } - var user = await _gmlManager.Users.GetUserByName(userName); + var user = await gmlManager.Users.GetUserByName(userName); if (user is null) { @@ -106,11 +75,11 @@ public async Task OnLeft(string userName) } Debug.WriteLine($"OnLeft: {userName}"); - await _gmlManager.Users.EndSession(user); + await gmlManager.Users.EndSession(user); } catch (Exception e) { - await KickUser(userName, "Произошла ошибка при попытке подключения к серверу"); + hubEvents.KickUser.OnNext((userName, "Произошла ошибка при попытке подключения к серверу")); Console.WriteLine(e); } diff --git a/src/Gml.Web.Api/Core/Hubs/HubEvents.cs b/src/Gml.Web.Api/Core/Hubs/HubEvents.cs index 1daa54b..efde2bf 100644 --- a/src/Gml.Web.Api/Core/Hubs/HubEvents.cs +++ b/src/Gml.Web.Api/Core/Hubs/HubEvents.cs @@ -1,8 +1,25 @@ +using System.Diagnostics; using System.Reactive.Subjects; +using Gml.Web.Api.Core.Hubs.Controllers; namespace Gml.Web.Api.Core.Hubs; public class HubEvents { - public ISubject KickUser { get; } = new Subject(); + public Subject<(string UserName, string Reason)> KickUser { get; } = new(); + + public HubEvents(PlayersController playersController) + { + KickUser.Subscribe(async void (user) => + { + try + { + await playersController.OnKickUser(user.UserName, user.Reason); + } + catch (Exception e) + { + //ToDo Add Sentry Log + } + }); + } } diff --git a/src/Gml.Web.Api/Core/Hubs/LauncherHub.cs b/src/Gml.Web.Api/Core/Hubs/LauncherHub.cs index ad55629..9663390 100644 --- a/src/Gml.Web.Api/Core/Hubs/LauncherHub.cs +++ b/src/Gml.Web.Api/Core/Hubs/LauncherHub.cs @@ -26,7 +26,7 @@ public LauncherHub( _profilesChangedEvent ??= gmlManager.Profiles.ProfilesChanged.Subscribe(eventType => { - foreach (var connection in _playerController.LauncherConnections.Values.Select(c => c.Connection).OfType()) + foreach (var connection in _playerController.LauncherInfos.Values.Select(c => c.Connection).OfType()) { connection?.SendAsync("RefreshProfiles"); } diff --git a/src/Gml.Web.Api/Core/MappingProfiles/PlayerMapper.cs b/src/Gml.Web.Api/Core/MappingProfiles/PlayerMapper.cs index ee99066..ce59b8a 100644 --- a/src/Gml.Web.Api/Core/MappingProfiles/PlayerMapper.cs +++ b/src/Gml.Web.Api/Core/MappingProfiles/PlayerMapper.cs @@ -9,6 +9,9 @@ public class PlayerMapper : Profile public PlayerMapper() { CreateMap(); + CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); } } diff --git a/src/Gml.Web.Api/Gml.Web.Api.csproj b/src/Gml.Web.Api/Gml.Web.Api.csproj index a2c3f13..7d1b703 100644 --- a/src/Gml.Web.Api/Gml.Web.Api.csproj +++ b/src/Gml.Web.Api/Gml.Web.Api.csproj @@ -142,6 +142,9 @@ ISentryHandler.cs + + IPlayersHandler.cs + From 2a530b47805b77c7d6c1f237ca43edc1eb551579 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sun, 8 Dec 2024 19:57:00 +0300 Subject: [PATCH 02/21] Add player whitelist management to profile endpoints Implemented POST and DELETE operations for managing the player whitelist within profiles. Users can now be added or removed from a profile's whitelist, enhancing the profile management capabilities. Authorization is required for these operations to ensure security and proper access control. --- .../Core/Extensions/EndpointsExtensions.cs | 26 +++++++++ .../Core/Handlers/ProfileHandler.cs | 56 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs index 7bbd131..cd95e02 100644 --- a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs +++ b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs @@ -588,6 +588,32 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .Produces((int)HttpStatusCode.BadRequest) .RequireAuthorization(); + app.MapPost("/api/v1/profiles/players/whitelist", ProfileHandler.AddPlayerToWhiteList) + .WithOpenApi(generatedOperation => + { + generatedOperation.Summary = "Добавление игрока в белый список профиля"; + return generatedOperation; + }) + .WithDescription("Добавление игрока в белый список профиля") + .WithName("Add users white list profile") + .WithTags("Profiles") + .Produces((int)HttpStatusCode.NotFound) + .Produces((int)HttpStatusCode.BadRequest) + .RequireAuthorization(); + + app.MapDelete("/api/v1/profiles/players/whitelist", ProfileHandler.RemovePlayerFromWhiteList) + .WithOpenApi(generatedOperation => + { + generatedOperation.Summary = "Удаление игрока из белого списка профиля"; + return generatedOperation; + }) + .WithDescription("Удаление игрока из белого списка профиля") + .WithName("Remove user from profile white list") + .WithTags("Profiles") + .Produces((int)HttpStatusCode.NotFound) + .Produces((int)HttpStatusCode.BadRequest) + .RequireAuthorization(); + app.MapPost("/api/v1/profiles/info", ProfileHandler.GetProfileInfo) .WithOpenApi(generatedOperation => { diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index f9e2caf..27a5ba5 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -385,4 +385,60 @@ public static async Task RemoveProfile( return Results.Ok(ResponseMessage.Create(message, HttpStatusCode.OK)); } + + [Authorize] + public static async Task AddPlayerToWhiteList( + IGmlManager gmlManager, + string profileName, + string userUuid) + { + var profile = await gmlManager.Profiles.GetProfile(profileName); + + if (profile is null) + return Results.NotFound(ResponseMessage.Create($"Профиль \"{profileName}\" не найден", + HttpStatusCode.NotFound)); + + var user = await gmlManager.Users.GetUserByUuid(userUuid); + + if (user is null) + return Results.NotFound(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден", + HttpStatusCode.NotFound)); + + if (profile.UserWhiteListGuid.Any()) + return Results.BadRequest(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" уже находится белом списке пользователей профиля", + HttpStatusCode.BadRequest)); + + profile.UserWhiteListGuid.Add(Guid.Parse(user.Uuid)); + await gmlManager.Profiles.SaveProfiles(); + + return Results.Ok(ResponseMessage.Create("Пользователь успешно добавлен в белый список профиля", HttpStatusCode.OK)); + } + + [Authorize] + public static async Task RemovePlayerFromWhiteList( + IGmlManager gmlManager, + string profileName, + string userUuid) + { + var profile = await gmlManager.Profiles.GetProfile(profileName); + + if (profile is null) + return Results.NotFound(ResponseMessage.Create($"Профиль \"{profileName}\" не найден", + HttpStatusCode.NotFound)); + + var user = await gmlManager.Users.GetUserByUuid(userUuid); + + if (user is null) + return Results.NotFound(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден", + HttpStatusCode.NotFound)); + + if (!profile.UserWhiteListGuid.Any()) + return Results.BadRequest(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден в белом списке пользователей профиля", + HttpStatusCode.BadRequest)); + + profile.UserWhiteListGuid.Remove(Guid.Parse(user.Uuid)); + await gmlManager.Profiles.SaveProfiles(); + + return Results.Ok(ResponseMessage.Create("Пользователь успешно удален из белого списка профиля", HttpStatusCode.OK)); + } } From a08ff7317016d0b9c0fff22888a719bd04847a3e Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sun, 8 Dec 2024 20:49:43 +0300 Subject: [PATCH 03/21] Add user whitelist to profile DTO Included a list of whitelisted users to the ProfileReadInfoDto by mapping user data to PlayerReadDto. Updated methods for adding and removing users from the whitelist to directly use the user's UUID. --- src/Gml.Web.Api.Dto/Player/PlayerReadDto.cs | 2 +- src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs | 2 ++ src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 10 +++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Gml.Web.Api.Dto/Player/PlayerReadDto.cs b/src/Gml.Web.Api.Dto/Player/PlayerReadDto.cs index e0ec5bd..ab0f1ca 100644 --- a/src/Gml.Web.Api.Dto/Player/PlayerReadDto.cs +++ b/src/Gml.Web.Api.Dto/Player/PlayerReadDto.cs @@ -4,8 +4,8 @@ namespace Gml.Web.Api.Dto.Player; public class PlayerReadDto : PlayerTextureDto { + public string Uuid { get; set; } public string Name { get; set; } = null!; public string AccessToken { get; set; } - public string Uuid { get; set; } public DateTime ExpiredDate { get; set; } } diff --git a/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs b/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs index d895f58..813e80d 100644 --- a/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs +++ b/src/Gml.Web.Api.Dto/Profile/ProfileReadInfoDto.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Gml.Web.Api.Dto.Files; +using Gml.Web.Api.Dto.Player; using GmlCore.Interfaces.Enums; using GmlCore.Interfaces.System; @@ -23,5 +24,6 @@ public class ProfileReadInfoDto public List Files { get; set; } public List WhiteListFolders { get; set; } public List WhiteListFiles { get; set; } + public List UsersWhiteList { get; set; } public string Background { get; set; } } diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index 27a5ba5..ec4a41d 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -10,6 +10,7 @@ using Gml.Web.Api.Domains.Exceptions; using Gml.Web.Api.Domains.System; using Gml.Web.Api.Dto.Messages; +using Gml.Web.Api.Dto.Player; using Gml.Web.Api.Dto.Profile; using GmlCore.Interfaces; using GmlCore.Interfaces.Enums; @@ -341,12 +342,15 @@ public static async Task GetProfileDetails( MinimumRamMb = createInfoDto.RamSize, OsName = osName, OsArch = createInfoDto.OsArchitecture - },user); + }, user); + + var whiteListPlayers = await gmlManager.Users.GetUsers(profile.UserWhiteListGuid); var profileDto = mapper.Map(profileInfo); profileDto.Background = $"{context.Request.Scheme}://{context.Request.Host}/api/v1/file/{profile.BackgroundImageKey}"; profileDto.IsEnabled = profile.IsEnabled; + profileDto.UsersWhiteList = mapper.Map>(whiteListPlayers); return Results.Ok(ResponseMessage.Create(profileDto, string.Empty, HttpStatusCode.OK)); } @@ -408,7 +412,7 @@ public static async Task AddPlayerToWhiteList( return Results.BadRequest(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" уже находится белом списке пользователей профиля", HttpStatusCode.BadRequest)); - profile.UserWhiteListGuid.Add(Guid.Parse(user.Uuid)); + profile.UserWhiteListGuid.Add(user.Uuid); await gmlManager.Profiles.SaveProfiles(); return Results.Ok(ResponseMessage.Create("Пользователь успешно добавлен в белый список профиля", HttpStatusCode.OK)); @@ -436,7 +440,7 @@ public static async Task RemovePlayerFromWhiteList( return Results.BadRequest(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден в белом списке пользователей профиля", HttpStatusCode.BadRequest)); - profile.UserWhiteListGuid.Remove(Guid.Parse(user.Uuid)); + profile.UserWhiteListGuid.Remove(user.Uuid); await gmlManager.Profiles.SaveProfiles(); return Results.Ok(ResponseMessage.Create("Пользователь успешно удален из белого списка профиля", HttpStatusCode.OK)); From cfc9543c7a7c5f1d47e1216930b4c2c4b9023fcc Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sun, 8 Dec 2024 21:37:53 +0300 Subject: [PATCH 04/21] Add endpoint for fetching user's head texture Introduce a new GET endpoint to retrieve the head texture of a user using their UUID. This includes adding the `GetUserHead` method in `TextureIntegrationHandler` and mapping the endpoint in `EndpointsExtensions`. Additionally, fix condition checks for whitelist validation in `ProfileHandler` for more precise user UUID matching. --- .../Core/Extensions/EndpointsExtensions.cs | 11 +++++++++++ src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 4 ++-- .../Core/Handlers/TextureIntegrationHandler.cs | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs index cd95e02..046f4b3 100644 --- a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs +++ b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs @@ -305,6 +305,17 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Integration/Textures") .Produces((int)HttpStatusCode.BadRequest); + app.MapGet("/api/v1/integrations/texture/head/{userUuid}", TextureIntegrationHandler.GetUserHead) + .WithOpenApi(generatedOperation => + { + generatedOperation.Summary = "Получение текстуры лица пользователя"; + return generatedOperation; + }) + .WithDescription("Получение текстуры лица пользователя") + .WithName("Get user head texture url") + .WithTags("Integration/Textures") + .Produces((int)HttpStatusCode.BadRequest); + app.MapGet("/api/v1/integrations/texture/cloaks", TextureIntegrationHandler.GetCloakUrl) .WithOpenApi(generatedOperation => { diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index ec4a41d..8144595 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -408,7 +408,7 @@ public static async Task AddPlayerToWhiteList( return Results.NotFound(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден", HttpStatusCode.NotFound)); - if (profile.UserWhiteListGuid.Any()) + if (profile.UserWhiteListGuid.Any(c => c.Equals(userUuid))) return Results.BadRequest(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" уже находится белом списке пользователей профиля", HttpStatusCode.BadRequest)); @@ -436,7 +436,7 @@ public static async Task RemovePlayerFromWhiteList( return Results.NotFound(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден", HttpStatusCode.NotFound)); - if (!profile.UserWhiteListGuid.Any()) + if (!profile.UserWhiteListGuid.Any(c=> c.Equals(userUuid))) return Results.BadRequest(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден в белом списке пользователей профиля", HttpStatusCode.BadRequest)); diff --git a/src/Gml.Web.Api/Core/Handlers/TextureIntegrationHandler.cs b/src/Gml.Web.Api/Core/Handlers/TextureIntegrationHandler.cs index 03da34b..232f661 100644 --- a/src/Gml.Web.Api/Core/Handlers/TextureIntegrationHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/TextureIntegrationHandler.cs @@ -169,4 +169,15 @@ public static async Task GetUserCloak(IGmlManager gmlManager, string te return Results.Stream(await gmlManager.Users.GetCloak(user)); } + + public static async Task GetUserHead(IGmlManager gmlManager, string userUuid) + { + var user = await gmlManager.Users.GetUserByUuid(userUuid); + + if (user is null) + return Results.NotFound(ResponseMessage.Create($"Пользователь с UUID: \"{userUuid}\" не найден", + HttpStatusCode.NotFound)); + + return Results.Stream(await gmlManager.Users.GetHead(user).ConfigureAwait(false)); + } } From 1b8bc238c8d0dd8b1fbeaae7e1c6c3aef9a0a942 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Mon, 9 Dec 2024 00:26:39 +0300 Subject: [PATCH 05/21] Add route parameters to whitelist endpoints Update the player whitelist endpoints to include profileName and userUuid as route parameters, enhancing the specificity and flexibility of the API. This change ensures that operations are correctly associated with the specified profile and player. --- src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs index 046f4b3..b70e16d 100644 --- a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs +++ b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs @@ -599,7 +599,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .Produces((int)HttpStatusCode.BadRequest) .RequireAuthorization(); - app.MapPost("/api/v1/profiles/players/whitelist", ProfileHandler.AddPlayerToWhiteList) + app.MapPost("/api/v1/profiles/{profileName}/players/whitelist/{userUuid}", ProfileHandler.AddPlayerToWhiteList) .WithOpenApi(generatedOperation => { generatedOperation.Summary = "Добавление игрока в белый список профиля"; @@ -612,7 +612,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .Produces((int)HttpStatusCode.BadRequest) .RequireAuthorization(); - app.MapDelete("/api/v1/profiles/players/whitelist", ProfileHandler.RemovePlayerFromWhiteList) + app.MapDelete("/api/v1/profiles/{profileName}/players/whitelist/{userUuid}", ProfileHandler.RemovePlayerFromWhiteList) .WithOpenApi(generatedOperation => { generatedOperation.Summary = "Удаление игрока из белого списка профиля"; From b2c29489e3b586b2f3bb4f521738dc8214f3c6ec Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Mon, 9 Dec 2024 08:00:57 +0300 Subject: [PATCH 06/21] Check whitelist before user access in ProfileHandler Add logic to check the user's whitelist and forbid access if the user's UUID is present in the profile's UserWhiteListGuid. This ensures users are blocked when they shouldn't have access to certain profiles. --- src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index 8144595..f823826 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -276,6 +276,9 @@ public static async Task GetProfileInfo( return Results.StatusCode(StatusCodes.Status403Forbidden); } + if (profile.UserWhiteListGuid.Any() && profile.UserWhiteListGuid.Any(c => c.Equals(user.Uuid))) + return Results.Forbid(); + user.Manager = gmlManager; var profileInfo = await gmlManager.Profiles.GetProfileInfo(profile.Name, new StartupOptions From 367133d019e405d43614b42e52b74504e098106f Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Mon, 9 Dec 2024 19:15:51 +0300 Subject: [PATCH 07/21] Enforce role-based authorization for API endpoints Updated API endpoints to require specific user roles for authorization, primarily limiting access to admin users. Adjusted user profile retrieval logic to filter game profiles based on user roles, enhancing security by ensuring that only authorized users can access or modify --- .../Core/Extensions/EndpointsExtensions.cs | 106 +++++++++--------- .../Core/Handlers/ProfileHandler.cs | 29 ++++- .../Core/Repositories/UserRepository.cs | 6 +- 3 files changed, 89 insertions(+), 52 deletions(-) diff --git a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs index b70e16d..dac5235 100644 --- a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs +++ b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs @@ -38,7 +38,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Integration/GitHub/Launcher") .Produces>>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/integrations/github/launcher/download", GitHubIntegrationHandler.DownloadLauncher) .WithOpenApi(generatedOperation => @@ -50,7 +50,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Integration/GitHub/Launcher") .Produces>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/integrations/github/launcher/download/{version}", GitHubIntegrationHandler.ReturnLauncherSolution) @@ -62,17 +62,21 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Download launcher solution") .WithTags("Integration/GitHub/Launcher") .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion #region SignalR Hubs - app.MapHub("/ws/profiles/restore").RequireAuthorization(); - app.MapHub("/ws/launcher/build").RequireAuthorization(); - app.MapHub("/ws/gameServer").RequireAuthorization(); + app.MapHub("/ws/profiles/restore") + .RequireAuthorization(c => c.RequireRole("Admin")); + app.MapHub("/ws/launcher/build") + .RequireAuthorization(c => c.RequireRole("Admin")); + app.MapHub("/ws/gameServer") + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapHub("/ws/launcher").RequireAuthorization(); - app.MapHub("/ws/notifications").RequireAuthorization(); + app.MapHub("/ws/notifications") + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -140,7 +144,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Update dsn sentry service url") .WithTags("Integration/Sentry") .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/{projectId}/envelope", SentryHandler.CreateBugInfo) .WithOpenApi(generatedOperation => @@ -150,7 +154,8 @@ public static WebApplication RegisterEndpoints(this WebApplication app) }) .WithDescription("Добавление ошибок Sentry") .WithName("Get sentry message") - .WithTags("Integration/Sentry"); + .WithTags("Integration/Sentry") + .RequireAuthorization(c => c.RequireRole("Admin", "Player")); app.MapPost("/api/v1/sentry", SentryHandler.GetBugs) .WithOpenApi(generatedOperation => @@ -161,7 +166,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение всех ошибок Sentry") .WithName("Get all bugs sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/sentry/filter", SentryHandler.GetFilterSentry) .WithOpenApi(generatedOperation => @@ -172,7 +177,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение отфильтрованного списка ошибок") .WithName("Get filtered bugs sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/sentry/filter/list", SentryHandler.GetFilterListSentry) .WithOpenApi(generatedOperation => @@ -183,7 +188,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение отфильтрованного списка по ошибок") .WithName("Get filtered on bugs sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/sentry/stats/last", SentryHandler.GetLastSentryErrors) .WithOpenApi(generatedOperation => @@ -194,7 +199,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение списка ошибок за последние 3 месяца") .WithName("Get last bugs sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/sentry/stats/summary", SentryHandler.GetSummarySentryErrors) .WithOpenApi(generatedOperation => @@ -205,7 +210,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получить сводку ошибок") .WithName("Get summary bugs sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/sentry/{exception}", SentryHandler.GetByException) .WithOpenApi(generatedOperation => @@ -216,7 +221,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение exception в Sentry") .WithName("Get exception on sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/sentry/bug/{id}", SentryHandler.GetBugId) .WithOpenApi(generatedOperation => @@ -227,7 +232,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение бага по Guid Sentry") .WithName("Get bug or id sentry") .WithTags("Integration/Sentry") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -254,7 +259,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Update discord RPC data") .WithTags("Integration/Discord") .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -281,7 +286,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Update skin texture url") .WithTags("Integration/Textures") .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/integrations/texture/skins/{textureGuid}", TextureIntegrationHandler.GetUserSkin) .WithOpenApi(generatedOperation => @@ -337,7 +342,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Update cloak texture url") .WithTags("Integration/Textures") .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/integrations/texture/skins/load", TextureIntegrationHandler.UpdateUserSkin) .WithOpenApi(generatedOperation => @@ -474,7 +479,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Integration/Auth") .Produces() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/integrations/auth", AuthIntegrationHandler.GetIntegrationServices) .WithOpenApi(generatedOperation => @@ -487,7 +492,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Integration/Auth") .Produces>>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/integrations/auth/active", AuthIntegrationHandler.GetAuthService) .WithOpenApi(generatedOperation => @@ -499,7 +504,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Get active auth service") .WithTags("Integration/Auth") .Produces>() - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/integrations/auth/active", AuthIntegrationHandler.RemoveAuthService) .WithOpenApi(generatedOperation => @@ -512,7 +517,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Integration/Auth") .Produces>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -530,7 +535,8 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Profiles list") .WithTags("Profiles") .Produces>>() - .Produces((int)HttpStatusCode.BadRequest); + .Produces((int)HttpStatusCode.BadRequest) + .RequireAuthorization(c => c.RequireRole("Player", "Admin")); app.MapGet("/api/v1/profiles/versions/{gameLoader}/{minecraftVersion}", ProfileHandler.GetMinecraftVersions) .WithOpenApi(generatedOperation => @@ -543,7 +549,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces>>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/profiles", ProfileHandler.CreateProfile) .WithOpenApi(generatedOperation => @@ -557,7 +563,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .Produces>() .Produces((int)HttpStatusCode.NotFound) .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPut("/api/v1/profiles", ProfileHandler.UpdateProfile) .WithOpenApi(generatedOperation => @@ -571,7 +577,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .Produces>() .Produces((int)HttpStatusCode.NotFound) .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/profiles/restore", ProfileHandler.RestoreProfile) .WithOpenApi(generatedOperation => @@ -584,7 +590,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces((int)HttpStatusCode.NotFound) .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/profiles/{profileNames}", ProfileHandler.RemoveProfile) .WithOpenApi(generatedOperation => @@ -597,7 +603,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces((int)HttpStatusCode.NotFound) .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/profiles/{profileName}/players/whitelist/{userUuid}", ProfileHandler.AddPlayerToWhiteList) .WithOpenApi(generatedOperation => @@ -610,7 +616,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces((int)HttpStatusCode.NotFound) .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/profiles/{profileName}/players/whitelist/{userUuid}", ProfileHandler.RemovePlayerFromWhiteList) .WithOpenApi(generatedOperation => @@ -623,7 +629,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces((int)HttpStatusCode.NotFound) .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/profiles/info", ProfileHandler.GetProfileInfo) .WithOpenApi(generatedOperation => @@ -648,7 +654,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/profiles/compile", ProfileHandler.CompileProfile) .WithOpenApi(generatedOperation => @@ -661,7 +667,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Profiles") .Produces>() .Produces((int)HttpStatusCode.BadRequest) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -704,7 +710,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Add file to white list") .WithTags("Files") .Produces((int)HttpStatusCode.NotFound) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/file/whiteList", FileHandler.RemoveFileWhiteList) .WithOpenApi(generatedOperation => @@ -716,7 +722,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Remove file from white list") .WithTags("Files") .Produces((int)HttpStatusCode.NotFound) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/folder/whiteList", FileHandler.AddFolderWhiteList) .WithOpenApi(generatedOperation => @@ -728,7 +734,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Add folder to white list") .WithTags("Files") .Produces((int)HttpStatusCode.NotFound) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/folder/whiteList", FileHandler.RemoveFolderWhiteList) .WithOpenApi(generatedOperation => @@ -740,7 +746,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Remove folder from white list") .WithTags("Files") .Produces((int)HttpStatusCode.NotFound) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -757,7 +763,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithTags("Settings") .Produces>() .Produces((int)HttpStatusCode.NotFound) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPut("/api/v1/settings/platform", SettingsHandler.UpdateSettings) @@ -770,7 +776,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Update settings") .WithTags("Settings") .Produces((int)HttpStatusCode.NotFound) - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -786,7 +792,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Установка плагина в систему") .WithName("Install plugin") .WithTags("Plugins") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/plugins", PluginHandler.GetInstalledPlugins) @@ -799,7 +805,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Get installed plugin") .WithTags("Plugins") .Produces>() - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/plugins/{name}/{version}", PluginHandler.RemovePlugin) @@ -811,7 +817,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Удаление плагина из системы") .WithName("Remove plugin") .WithTags("Plugins") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -826,7 +832,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Загрузка новой версии лаунчера") .WithName("Upload launcher version") .WithTags("Launcher") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/launcher", LauncherUpdateHandler.GetActualVersion) .WithOpenApi(generatedOperation => @@ -847,7 +853,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение списка сборок") .WithName("Get launcher builds") .WithTags("Launcher") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapGet("/api/v1/launcher/platforms", LauncherUpdateHandler.GetPlatforms) .WithOpenApi(generatedOperation => @@ -858,7 +864,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Получение списка платформ для сборки") .WithName("Get launcher platforms") .WithTags("Launcher") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -874,7 +880,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Get profile game servers") .WithTags("MinecraftServers") .Produces>>() - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapPost("/api/v1/servers/{profileName}", ServersHandler.CreateServer) .WithOpenApi(generatedOperation => @@ -885,7 +891,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Создание сервера у профиля") .WithName("Create server to game profile") .WithTags("MinecraftServers") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/servers/{profileName}/{serverNamesString}", ServersHandler.RemoveServer) .WithOpenApi(generatedOperation => @@ -896,7 +902,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithDescription("Удаление сервера в игровом профиле") .WithName("Remove server from game profile") .WithTags("MinecraftServers") - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion @@ -912,7 +918,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Get profile notifications") .WithTags("Notifications") .Produces>>() - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); app.MapDelete("/api/v1/notifications", NotificationHandler.ClearNotification) .WithOpenApi(generatedOperation => @@ -924,7 +930,7 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .WithName("Delete all notifications") .WithTags("Notifications") .Produces() - .RequireAuthorization(); + .RequireAuthorization(c => c.RequireRole("Admin")); #endregion diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index f823826..2cc45c4 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -28,7 +28,34 @@ public static async Task GetProfiles( IMapper mapper, IGmlManager gmlManager) { - var profiles = await gmlManager.Profiles.GetProfiles(); + IEnumerable profiles = []; + + if (context.User.IsInRole("Player")) + { + var userName = context.User.Identity?.Name; + + if (string.IsNullOrEmpty(userName)) + { + return Results.BadRequest(ResponseMessage.Create("Не удалось идентифицировать пользователя", + HttpStatusCode.BadRequest)); + } + + var user = await gmlManager.Users.GetUserByName(userName); + + if (user is null) + { + return Results.BadRequest(ResponseMessage.Create("Не удалось идентифицировать пользователя", + HttpStatusCode.BadRequest)); + } + + profiles = (await gmlManager.Profiles.GetProfiles()) + .Where(c => c.IsEnabled || c.UserWhiteListGuid + .Any(g => g.Equals(user.Uuid))); + }else if (context.User.IsInRole("Admin")) + { + profiles = await gmlManager.Profiles.GetProfiles(); + } + var gameProfiles = profiles as IGameProfile[] ?? profiles.ToArray(); var dtoProfiles = mapper.Map(profiles); diff --git a/src/Gml.Web.Api/Core/Repositories/UserRepository.cs b/src/Gml.Web.Api/Core/Repositories/UserRepository.cs index c52f007..52dbec2 100644 --- a/src/Gml.Web.Api/Core/Repositories/UserRepository.cs +++ b/src/Gml.Web.Api/Core/Repositories/UserRepository.cs @@ -70,7 +70,11 @@ private string GetAccessToken(string userId) var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { - Subject = new ClaimsIdentity(new[] { new Claim("id", userId) }), + Subject = new ClaimsIdentity(new[] + { + new Claim("id", userId), + new Claim(ClaimTypes.Role, "Admin"), + }), Expires = DateTime.UtcNow.AddDays(3), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecurityKey)), From c0b1f7208ef606d5b9211546bafe6a7edd6323cf Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Mon, 9 Dec 2024 19:16:23 +0300 Subject: [PATCH 08/21] Update submodule link Gml.Core --- src/Gml.Core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Core b/src/Gml.Core index 20fa9fc..d8f8eda 160000 --- a/src/Gml.Core +++ b/src/Gml.Core @@ -1 +1 @@ -Subproject commit 20fa9fc8826e1a687e11ca659611111d912603d1 +Subproject commit d8f8eda6c71bfebd26fb7f4742229df009f6c152 From aba4b334814877d30b93ff0facc93e7fd144fac6 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Thu, 19 Dec 2024 20:43:36 +0300 Subject: [PATCH 09/21] Change data types for Real and Virtual to decimal Updated the `Real` and `Virtual` properties from `int` to `decimal` in `UnicoreAuthResult`. This ensures better precision for these values, accommodating scenarios requiring fractional numbers. --- src/Gml.Web.Api.Domains/Integrations/UnicoreAuthResult.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gml.Web.Api.Domains/Integrations/UnicoreAuthResult.cs b/src/Gml.Web.Api.Domains/Integrations/UnicoreAuthResult.cs index 62e453c..0dcb62b 100644 --- a/src/Gml.Web.Api.Domains/Integrations/UnicoreAuthResult.cs +++ b/src/Gml.Web.Api.Domains/Integrations/UnicoreAuthResult.cs @@ -60,10 +60,10 @@ public class User public string TwoFactorSecretTemp { get; set; } [JsonProperty("real")] - public int Real { get; set; } + public decimal Real { get; set; } [JsonProperty("virtual")] - public int Virtual { get; set; } + public decimal Virtual { get; set; } [JsonProperty("perms")] public object Perms { get; set; } From a3dee24ed18272ae4582b9b1d9a54cd31fe61ad8 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Thu, 19 Dec 2024 20:44:02 +0300 Subject: [PATCH 10/21] Change `Money` property type from `int` to `decimal`. This update ensures better precision for monetary values in the `AzuriomAuthResult` class. It prevents potential inaccuracies that might occur with integer-based representations of currency. --- src/Gml.Web.Api.Domains/Integrations/AzuriomAuthResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Web.Api.Domains/Integrations/AzuriomAuthResult.cs b/src/Gml.Web.Api.Domains/Integrations/AzuriomAuthResult.cs index 548b7c0..5eb628d 100644 --- a/src/Gml.Web.Api.Domains/Integrations/AzuriomAuthResult.cs +++ b/src/Gml.Web.Api.Domains/Integrations/AzuriomAuthResult.cs @@ -18,7 +18,7 @@ public class AzuriomAuthResult public bool EmailVerified { get; set; } [JsonProperty("money")] - public int Money { get; set; } + public decimal Money { get; set; } [JsonProperty("banned")] public bool Banned { get; set; } From fb01053ca82a611f61c0195f823afb8ecc189aed Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Thu, 19 Dec 2024 21:17:27 +0300 Subject: [PATCH 11/21] Add player ban and pardon functionality with related checks Implemented ban and pardon endpoints to manage player access. Updated handlers and authentication logic to respect the ban status by blocking banned players from logging in or joining a server. Enhanced the system to handle empty input lists for ban/pardon actions with appropriate error responses. --- .../Core/Extensions/EndpointsExtensions.cs | 24 ++++++++++ .../Core/Handlers/AuthIntegrationHandler.cs | 14 ++++++ .../Core/Handlers/MinecraftHandler.cs | 4 +- .../Core/Handlers/PlayersHandler.cs | 47 +++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs index dac5235..78830cb 100644 --- a/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs +++ b/src/Gml.Web.Api/Core/Extensions/EndpointsExtensions.cs @@ -685,6 +685,30 @@ public static WebApplication RegisterEndpoints(this WebApplication app) .Produces>>() .Produces((int)HttpStatusCode.BadRequest); + app.MapPost("/api/v1/players/ban", PlayersHandler.BanPlayer) + .WithOpenApi(generatedOperation => + { + generatedOperation.Summary = "Блокировка списка игроков"; + return generatedOperation; + }) + .WithDescription("Блокировка списка игроков") + .WithName("Ban players") + .WithTags("Players") + .Produces>>() + .Produces((int)HttpStatusCode.BadRequest); + + app.MapPost("/api/v1/players/pardon", PlayersHandler.PardonPlayer) + .WithOpenApi(generatedOperation => + { + generatedOperation.Summary = "Разблокировка списка игроков"; + return generatedOperation; + }) + .WithDescription("Разблокировка списка игроков") + .WithName("Pardon players") + .WithTags("Players") + .Produces>>() + .Produces((int)HttpStatusCode.BadRequest); + #endregion #region Files diff --git a/src/Gml.Web.Api/Core/Handlers/AuthIntegrationHandler.cs b/src/Gml.Web.Api/Core/Handlers/AuthIntegrationHandler.cs index c14dc22..f971e30 100644 --- a/src/Gml.Web.Api/Core/Handlers/AuthIntegrationHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/AuthIntegrationHandler.cs @@ -60,6 +60,13 @@ public static async Task Auth( authResult.Uuid, context.Request.Headers["X-HWID"]); + if (player.IsBanned) + { + return Results.BadRequest(ResponseMessage.Create( + "Пользователь заблокирован!", + HttpStatusCode.BadRequest)); + } + await gmlManager.Profiles.CreateUserSessionAsync(null, player); player.TextureSkinUrl ??= (await gmlManager.Integrations.GetSkinServiceAsync()) @@ -116,6 +123,13 @@ public static async Task AuthWithToken( { var player = user; + if (player.IsBanned) + { + return Results.BadRequest(ResponseMessage.Create( + "Пользователь заблокирован!", + HttpStatusCode.BadRequest)); + } + player.TextureSkinUrl ??= (await gmlManager.Integrations.GetSkinServiceAsync()) .Replace("{userName}", player.Name) .Replace("{userUuid}", player.Uuid); diff --git a/src/Gml.Web.Api/Core/Handlers/MinecraftHandler.cs b/src/Gml.Web.Api/Core/Handlers/MinecraftHandler.cs index deb42b3..10461fc 100644 --- a/src/Gml.Web.Api/Core/Handlers/MinecraftHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/MinecraftHandler.cs @@ -72,7 +72,7 @@ public static async Task HasJoined(HttpContext context, IGmlManager gml { var user = await gmlManager.Users.GetUserByName(userName); - if (user is null || string.IsNullOrEmpty(userName) || await gmlManager.Users.CanJoinToServer(user, serverId) == false) + if (user is null || string.IsNullOrEmpty(userName) || user.IsBanned || await gmlManager.Users.CanJoinToServer(user, serverId) == false) return Results.NoContent(); var profile = new Profile @@ -147,7 +147,7 @@ public static async Task GetProfile(HttpContext context, IGmlManager gm var user = await gmlManager.Users.GetUserByUuid(guidUuid); - if (user is null || string.IsNullOrEmpty(guidUuid)) return Results.NoContent(); + if (user is null || string.IsNullOrEmpty(guidUuid) || user.IsBanned) return Results.NoContent(); var profile = new Profile { diff --git a/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs b/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs index ac6bc0e..13a8ead 100644 --- a/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/PlayersHandler.cs @@ -14,4 +14,51 @@ public static async Task GetPlayers(IGmlManager gmlManager, IMapper map return Results.Ok(ResponseMessage.Create(mapper.Map>(players), "Список пользователей успешно получен", HttpStatusCode.OK)); } + + public static async Task BanPlayer( + IGmlManager gmlManager, + IMapper mapper, + IList playerUuids, + bool deviceBlock = false) + { + if (!playerUuids.Any()) + { + return Results.BadRequest(ResponseMessage.Create("Не передан ни один пользователь для блокировки", + HttpStatusCode.BadRequest)); + } + + foreach (var playerUuid in playerUuids) + { + var player = await gmlManager.Users.GetUserByUuid(playerUuid); + + if (player is null) continue; + player.IsBanned = true; + await gmlManager.Users.UpdateUser(player); + } + + return Results.Ok(ResponseMessage.Create("Пользователь(и) успешно заблокированы", HttpStatusCode.OK)); + } + + public static async Task PardonPlayer( + IGmlManager gmlManager, + IMapper mapper, + IList playerUuids) + { + if (!playerUuids.Any()) + { + return Results.BadRequest(ResponseMessage.Create("Не передан ни один пользователь для блокировки", + HttpStatusCode.BadRequest)); + } + + foreach (var playerUuid in playerUuids) + { + var player = await gmlManager.Users.GetUserByUuid(playerUuid); + + if (player is null) continue; + player.IsBanned = false; + await gmlManager.Users.UpdateUser(player); + } + + return Results.Ok(ResponseMessage.Create("Пользователь(и) успешно разблокированы", HttpStatusCode.OK)); + } } From c50f5d92f281e5fca9a49b017016d4e8daebcb58 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Thu, 19 Dec 2024 21:19:02 +0300 Subject: [PATCH 12/21] Restrict access for banned users in ProfileHandler. Added a check to deny access in ProfileHandler if a user is banned. This improves security and ensures banned users cannot perform unauthorized actions. --- src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index 2cc45c4..c0888e8 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -298,7 +298,7 @@ public static async Task GetProfileInfo( var user = await gmlManager.Users.GetUserByName(createInfoDto.UserName); - if (user is null || user.AccessToken != token) + if (user is null || user.AccessToken != token || user.IsBanned) { return Results.StatusCode(StatusCodes.Status403Forbidden); } From da04daac0e489a5fea8c4644f5dd1f287311a4fb Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Thu, 19 Dec 2024 21:32:12 +0300 Subject: [PATCH 13/21] Refine profile filtering logic in ProfileHandler Adjust the filtering condition to ensure profiles are correctly retrieved based on `IsEnabled` status and `UserWhiteListGuid` count. This improves clarity and accuracy in determining accessible profiles for users. --- src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index c0888e8..610ebb4 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -49,8 +49,10 @@ public static async Task GetProfiles( } profiles = (await gmlManager.Profiles.GetProfiles()) - .Where(c => c.IsEnabled || c.UserWhiteListGuid - .Any(g => g.Equals(user.Uuid))); + .Where(c => + c is { IsEnabled: true, UserWhiteListGuid.Count: 0 } || + c.UserWhiteListGuid.Any(g => g.Equals(user.Uuid))); + }else if (context.User.IsInRole("Admin")) { profiles = await gmlManager.Profiles.GetProfiles(); From 0233a29e935b2a922e06dbcb4fdc78a4dd1f63b7 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Thu, 19 Dec 2024 21:35:52 +0300 Subject: [PATCH 14/21] Update submodule link Gml.Core --- src/Gml.Core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Core b/src/Gml.Core index d8f8eda..52ee3f8 160000 --- a/src/Gml.Core +++ b/src/Gml.Core @@ -1 +1 @@ -Subproject commit d8f8eda6c71bfebd26fb7f4742229df009f6c152 +Subproject commit 52ee3f81a1c0629e35a20ccbf8df3cf035011303 From ff6cf87b7b91a4830b9f44901459db4c111b1b0e Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sat, 21 Dec 2024 16:38:10 +0300 Subject: [PATCH 15/21] Handle exceptions in PluginMiddleware gracefully. Wrap the middleware logic in a try-catch block to capture exceptions and report them to the bug tracker. Respond with a proper error message and status code when a failure occurs. This ensures the application handles errors more robustly. --- .../Core/Middlewares/PluginMiddleware.cs | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Gml.Web.Api/Core/Middlewares/PluginMiddleware.cs b/src/Gml.Web.Api/Core/Middlewares/PluginMiddleware.cs index bdaa52a..aff90e6 100644 --- a/src/Gml.Web.Api/Core/Middlewares/PluginMiddleware.cs +++ b/src/Gml.Web.Api/Core/Middlewares/PluginMiddleware.cs @@ -1,10 +1,12 @@ using System.Diagnostics; +using System.Net; using System.Reactive.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; using Gml.Web.Api.Core.Services; +using Gml.Web.Api.Dto.Messages; using Gml.Web.Api.EndpointSDK; using GmlCore.Interfaces; using Spectre.Console; @@ -26,24 +28,32 @@ public PluginMiddleware(RequestDelegate next, AccessTokenService accessTokenServ public async Task Invoke(HttpContext context) { - var reference = await Process(context); - - if (reference is null) + try { - return; - } + var reference = await Process(context); - if (!context.Response.HasStarted) - { - await _next(context); - } + if (reference is null) + { + return; + } - for (int i = 0; i < 10 && reference.IsAlive; i++) - { - GC.Collect(); - GC.WaitForPendingFinalizers(); + if (!context.Response.HasStarted) + { + await _next(context); + } + + for (int i = 0; i < 10 && reference.IsAlive; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); - // Debug.WriteLine($"Clean GC: {i}/10"); + // Debug.WriteLine($"Clean GC: {i}/10"); + } + } + catch (Exception exeption) + { + _gmlManager.BugTracker.CaptureException(exeption); + await context.Response.WriteAsJsonAsync(ResponseMessage.Create("Сервер обработал принял запрос, но не смог его обработать", HttpStatusCode.UnprocessableContent)); } // Debug.WriteLine($"Unload successful: {!reference.IsAlive}"); From b922411c617f72c63af4286c79c3e2c1381af91d Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sat, 21 Dec 2024 16:38:21 +0300 Subject: [PATCH 16/21] Add mapping for user data in AddPlayerToWhiteList Integrated IMapper to map the user entity to PlayerReadDto when adding a player to the whitelist. Updated the response to include the mapped user data for improved clarity and consistency in the API output. --- src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index 610ebb4..2ec4d55 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -425,6 +425,7 @@ public static async Task RemoveProfile( [Authorize] public static async Task AddPlayerToWhiteList( IGmlManager gmlManager, + IMapper mapper, string profileName, string userUuid) { @@ -447,7 +448,9 @@ public static async Task AddPlayerToWhiteList( profile.UserWhiteListGuid.Add(user.Uuid); await gmlManager.Profiles.SaveProfiles(); - return Results.Ok(ResponseMessage.Create("Пользователь успешно добавлен в белый список профиля", HttpStatusCode.OK)); + var mappedUser = mapper.Map(user); + + return Results.Ok(ResponseMessage.Create(mappedUser, "Пользователь успешно добавлен в белый список профиля", HttpStatusCode.OK)); } [Authorize] From 853bf69383cf39ec5f8fb593e1d12d922d598da5 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Sat, 28 Dec 2024 04:42:18 +0300 Subject: [PATCH 17/21] Update submodule link Gml.Core --- src/Gml.Core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Core b/src/Gml.Core index 52ee3f8..87ae925 160000 --- a/src/Gml.Core +++ b/src/Gml.Core @@ -1 +1 @@ -Subproject commit 52ee3f81a1c0629e35a20ccbf8df3cf035011303 +Subproject commit 87ae925d52d85664429d0bcc3f2737d6d1ca354c From 039739fc1d86ea80c389447bc7904949e23f6b63 Mon Sep 17 00:00:00 2001 From: PashkovD <77713594+PashkovD@users.noreply.github.com> Date: Tue, 31 Dec 2024 01:06:26 +0300 Subject: [PATCH 18/21] Protected skin url from null --- src/Gml.Web.Api.Dto/Minecraft/AuthLib/Textures.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Web.Api.Dto/Minecraft/AuthLib/Textures.cs b/src/Gml.Web.Api.Dto/Minecraft/AuthLib/Textures.cs index 354063e..89ba98c 100644 --- a/src/Gml.Web.Api.Dto/Minecraft/AuthLib/Textures.cs +++ b/src/Gml.Web.Api.Dto/Minecraft/AuthLib/Textures.cs @@ -4,7 +4,7 @@ namespace Gml.Web.Api.Dto.Minecraft.AuthLib; public class Textures { - [JsonProperty("SKIN")] public SkinCape Skin { get; set; } + [JsonProperty("SKIN", NullValueHandling = NullValueHandling.Ignore)] public SkinCape Skin { get; set; } [JsonProperty("CAPE", NullValueHandling = NullValueHandling.Ignore)] public SkinCape Cape { get; set; } } From f6ed2b36aeade8c25540125cf45ff4e5b6b31915 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Tue, 31 Dec 2024 07:50:48 +0300 Subject: [PATCH 19/21] Update TestAuthenticationHandler to include Admin role claim Modified the TestAuthenticationHandler to assign an "Admin" role claim to the identity during authentication. This adjustment helps simulate role-based scenarios in testing environments. --- src/Gml.Web.Api/Core/Middlewares/TestAuthenticationHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Web.Api/Core/Middlewares/TestAuthenticationHandler.cs b/src/Gml.Web.Api/Core/Middlewares/TestAuthenticationHandler.cs index 65ddfd8..3c6a364 100644 --- a/src/Gml.Web.Api/Core/Middlewares/TestAuthenticationHandler.cs +++ b/src/Gml.Web.Api/Core/Middlewares/TestAuthenticationHandler.cs @@ -19,7 +19,7 @@ public TestAuthenticationHandler(IOptionsMonitor op protected override Task HandleAuthenticateAsync() { - var identity = new ClaimsIdentity(Array.Empty(), "Test"); + var identity = new ClaimsIdentity([new Claim(ClaimTypes.Role, "Admin")], "Test"); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, "TestScheme"); From 0aeaf5220ddadeffbae359bedd7d0ffa9dd57452 Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Tue, 31 Dec 2024 08:21:58 +0300 Subject: [PATCH 20/21] Update submodule link Gml.Web.Api --- src/Gml.Core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Core b/src/Gml.Core index 87ae925..3da3c8d 160000 --- a/src/Gml.Core +++ b/src/Gml.Core @@ -1 +1 @@ -Subproject commit 87ae925d52d85664429d0bcc3f2737d6d1ca354c +Subproject commit 3da3c8dae2d4213fab47afb24ddbc1004c5af81a From 680cff6fd9afa004fc8e05562a64c93ca9563a8f Mon Sep 17 00:00:00 2001 From: "terentev.a.a" Date: Tue, 31 Dec 2024 15:30:47 +0300 Subject: [PATCH 21/21] Fix profile whitelist check with case-insensitive comparison Update the profile whitelist logic to use a case-insensitive UUID comparison and correct the condition for non-empty whitelist checks. This ensures proper validation and prevents potential mismatches due to casing issues. --- src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs index 2ec4d55..f8d0491 100644 --- a/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs +++ b/src/Gml.Web.Api/Core/Handlers/ProfileHandler.cs @@ -305,7 +305,7 @@ public static async Task GetProfileInfo( return Results.StatusCode(StatusCodes.Status403Forbidden); } - if (profile.UserWhiteListGuid.Any() && profile.UserWhiteListGuid.Any(c => c.Equals(user.Uuid))) + if (profile.UserWhiteListGuid.Count != 0 && !profile.UserWhiteListGuid.Any(c => c.Equals(user.Uuid, StringComparison.OrdinalIgnoreCase))) return Results.Forbid(); user.Manager = gmlManager;