Skip to content

Commit

Permalink
Merge pull request #292 from alborrajo/feature/chatrpc
Browse files Browse the repository at this point in the history
Improvements to Chat RPC commands
  • Loading branch information
alborrajo authored May 11, 2024
2 parents 9b80115 + c11098b commit ab85b98
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Arrowgene.Ddon.GameServer/Chat/ChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public ChatMessage(LobbyChatMsgType messageType, byte unk2, uint unk3, uint unk4
Deliver = true;
}

public LobbyChatMsgType Type { get; }
public LobbyChatMsgType Type { get; set; }
public byte Unk2 { get; set; }
public uint Unk3 { get; set; }
public uint Unk4 { get; set; }
Expand Down
8 changes: 8 additions & 0 deletions Arrowgene.Ddon.GameServer/Chat/Log/ChatLogHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,13 @@ public void Handle(GameClient client, ChatMessage message, List<ChatResponse> re
ChatMessageLogEntry logEntry = new ChatMessageLogEntry(client.Character, message);
_chatMessageLog.Add(logEntry);
}

public void AddEntry(uint characterId, string firstName, string lastName, ChatMessage message)
{
Logger.Info("Chat message: "+message.Message);

ChatMessageLogEntry logEntry = new ChatMessageLogEntry(characterId, firstName, lastName, message);
_chatMessageLog.Add(logEntry);
}
}
}
10 changes: 10 additions & 0 deletions Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ public ChatMessageLogEntry()
{
}

public ChatMessageLogEntry(uint characterId, string firstName, string lastName, ChatMessage chatMessage)
{
DateTime = DateTime.UtcNow;
FirstName = firstName;
LastName = lastName;
CharacterId = characterId;
ChatMessage = chatMessage;
}

public ChatMessageLogEntry(Character character, ChatMessage chatMessage)
{
DateTime = DateTime.UtcNow;
Expand All @@ -20,6 +29,7 @@ public ChatMessageLogEntry(Character character, ChatMessage chatMessage)
}

public DateTime DateTime { get; set; }
public long UnixTimeMillis { get => ((DateTimeOffset) DateTime.SpecifyKind(this.DateTime, DateTimeKind.Utc)).ToUnixTimeMilliseconds(); }
public string FirstName { get; set; }
public string LastName { get; set; }
public uint CharacterId { get; set; }
Expand Down
105 changes: 105 additions & 0 deletions Arrowgene.Ddon.Rpc.Web/Middleware/AuthMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Arrowgene.Ddon.Database;
using Arrowgene.Ddon.Database.Model;
using Arrowgene.Ddon.Shared.Crypto;
using Arrowgene.Logging;
using Arrowgene.WebServer;
using Arrowgene.WebServer.Middleware;

public class AuthMiddleware : IWebMiddleware
{
private static readonly ILogger Logger = LogProvider.Logger<Logger>(typeof(AuthMiddleware));

private readonly IDatabase _database;
private readonly Dictionary<string, AccountStateType> _routeAndRequiredMinimumState;

public AuthMiddleware(IDatabase database)
{
_database = database;
_routeAndRequiredMinimumState = new Dictionary<string, AccountStateType>();
}

public void Require(AccountStateType minimumState, string route)
{
_routeAndRequiredMinimumState.Add(route, minimumState);
}

public async Task<WebResponse> Handle(WebRequest request, WebMiddlewareDelegate next)
{
if(!_routeAndRequiredMinimumState.ContainsKey(request.Path))
{
// Don't intercept request if the request path isn't registered in the middleware
return await next(request);
}

string authHeader = request.Header.Get("authorization");
if(authHeader == null)
{
Logger.Error("Attempted to access auth protected route with no Authorization header");
WebResponse response = new WebResponse();
response.StatusCode = 401;
await response.WriteAsync("Attempted to access auth protected route with no Authorization header");
return response;
}

if(!authHeader.StartsWith("Basic "))
{
Logger.Error("Attempted to access auth protected route with an invalid Authorization method. Only Basic auth is supported.");
WebResponse response = new WebResponse();
response.StatusCode = 401;
await response.WriteAsync("Attempted to access auth protected route with an invalid Authorization method. Only Basic auth is supported.");
return response;
}

string encodedUserAndPassword = authHeader.Substring("Basic ".Length);
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string[] usernameAndPassword = encoding.GetString(Convert.FromBase64String(encodedUserAndPassword)).Split(":");
if(usernameAndPassword.Length != 2)
{
Logger.Error("Attempted to access auth protected route with an invalid Basic auth header.");
WebResponse response = new WebResponse();
response.StatusCode = 401;
await response.WriteAsync("Attempted to access auth protected route with an invalid Basic auth header.");
return response;
}

string username = usernameAndPassword[0];
string password = usernameAndPassword[1];

Account account = _database.SelectAccountByName(username);
if (account == null)
{
Logger.Error($"Attempted to authenticate as a nonexistant user {username}.");
WebResponse response = new WebResponse();
response.StatusCode = 401;
await response.WriteAsync($"Failed to authenticate as {username}.");
return response;
}

if (!PasswordHash.Verify(password, account.Hash))
{
Logger.Error($"Attempted to authenticate as {username} with an incorrect password.");
WebResponse response = new WebResponse();
response.StatusCode = 401;
await response.WriteAsync($"Failed to authenticate as {username}.");
return response;
}

AccountStateType minimumRequiredAccountStateType = _routeAndRequiredMinimumState[request.Path];
if(account.State < minimumRequiredAccountStateType)
{
Logger.Error($"Attempted to access auth protected route as {username} without enough permissions (Account has {account.State}, minimum required {minimumRequiredAccountStateType}).");
WebResponse response = new WebResponse();
response.StatusCode = 403;
await response.WriteAsync($"Attempted to access auth protected route as {username} without enough permissions.");
return response;
}

return await next(request);
}
}
17 changes: 15 additions & 2 deletions Arrowgene.Ddon.Rpc.Web/Route/ChatRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Arrowgene.WebServer;
using System.Text.Json;
using Arrowgene.Ddon.GameServer.Chat.Log;
using static Arrowgene.Ddon.GameServer.Chat.ChatManager;

namespace Arrowgene.Ddon.Rpc.Web.Route
{
Expand All @@ -27,7 +26,7 @@ public override async Task<WebResponse> Get(WebRequest request)
{
string dateString = request.QueryParameter.Get("since");
try {
chat = new ChatCommand(DateTime.Parse(dateString));
chat = new ChatCommand(ParseSinceDate(dateString));
}
catch(FormatException e)
{
Expand Down Expand Up @@ -85,5 +84,19 @@ public override async Task<WebResponse> Post(WebRequest request)
}
}

private static long ParseSinceDate(string dateString)
{
if(DateTime.TryParse(dateString, out DateTime date))
{
return ((DateTimeOffset) DateTime.SpecifyKind(date, DateTimeKind.Utc)).ToUnixTimeMilliseconds();
}

if(long.TryParse(dateString, out long unixTimeMillis))
{
return unixTimeMillis;
}

throw new FormatException();
}
}
}
11 changes: 9 additions & 2 deletions Arrowgene.Ddon.Rpc.Web/RpcWebServer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Arrowgene.Ddon.GameServer;
using Arrowgene.Ddon.Database.Model;
using Arrowgene.Ddon.GameServer;
using Arrowgene.Ddon.Rpc.Web.Route;
using Arrowgene.Ddon.WebServer;

Expand All @@ -20,7 +21,13 @@ public void Init()
{
_webServer.AddRoute(new SpawnRoute(this));
_webServer.AddRoute(new InfoRoute(this));
_webServer.AddRoute(new ChatRoute(this));

ChatRoute chatRoute = new ChatRoute(this);
_webServer.AddRoute(chatRoute);

AuthMiddleware authMiddleware = new AuthMiddleware(_gameServer.Database);
authMiddleware.Require(AccountStateType.GameMaster, chatRoute.Route);
_webServer.AddMiddleware(authMiddleware);
}
}
}
10 changes: 5 additions & 5 deletions Arrowgene.Ddon.Rpc/Command/ChatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ public class ChatCommand : IRpcCommand

public ChatCommand()
{
_since = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
_sinceUnixMillis = long.MinValue;
}

public ChatCommand(DateTime since)
public ChatCommand(long sinceUnixMillis)
{
_since = since;
_sinceUnixMillis = sinceUnixMillis;
}

public IEnumerable<ChatMessageLogEntry> ChatMessageLog { get; set; }

private readonly DateTime _since;
private readonly long _sinceUnixMillis;

public RpcCommandResult Execute(DdonGameServer gameServer)
{
ChatMessageLog = gameServer.ChatLogHandler.ChatMessageLog
.Where(entry => entry.DateTime >= _since);
.Where(entry => entry.UnixTimeMillis > _sinceUnixMillis);
return new RpcCommandResult(this, true);
}
}
Expand Down
3 changes: 2 additions & 1 deletion Arrowgene.Ddon.Rpc/Command/ChatPostCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public RpcCommandResult Execute(DdonGameServer gameServer)
_entry.LastName,
_entry.ChatMessage.Type,
gameServer.ClientLookup.GetAll()
);
);
gameServer.ChatLogHandler.AddEntry(0, _entry.FirstName, _entry.LastName, _entry.ChatMessage);
return new RpcCommandResult(this, true);
}

Expand Down
2 changes: 1 addition & 1 deletion Arrowgene.Ddon.Rpc/RpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class RpcServer : IRpcExecuter
{
private static readonly ILogger Logger = LogProvider.Logger<Logger>(typeof(RpcServer));

private readonly DdonGameServer _gameServer;
protected readonly DdonGameServer _gameServer;

public RpcServer(DdonGameServer gameServer)
{
Expand Down
1 change: 1 addition & 0 deletions Arrowgene.Ddon.Test/Arrowgene.Ddon.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<ItemGroup>
<ProjectReference Include="..\Arrowgene.Ddon.Client\Arrowgene.Ddon.Client.csproj" />
<ProjectReference Include="..\Arrowgene.Ddon.Cli\Arrowgene.Ddon.Cli.csproj" />
<ProjectReference Include="..\Arrowgene.Ddon.GameServer\Arrowgene.Ddon.GameServer.csproj" />
<ProjectReference Include="..\Arrowgene.Ddon.Server\Arrowgene.Ddon.Server.csproj" />
</ItemGroup>
<ItemGroup>
Expand Down
22 changes: 22 additions & 0 deletions Arrowgene.Ddon.Test/GameServer/Chat/Log/ChatMessageLogEntryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text.Json;
using Arrowgene.Ddon.GameServer.Chat;
using Arrowgene.Ddon.GameServer.Chat.Log;
using Arrowgene.Ddon.Shared.Model;
using Xunit;

namespace Arrowgene.Ddon.Test.GameServer.Chat.Log;

public class ChatMessageLogEntryTest
{
[Fact]
public void TestJsonSerialize()
{
ChatMessageLogEntry obj = new ChatMessageLogEntry();
obj.ChatMessage = new ChatMessage();
obj.ChatMessage.Type = LobbyChatMsgType.Party;
string json = JsonSerializer.Serialize(obj);
ChatMessageLogEntry res = JsonSerializer.Deserialize<ChatMessageLogEntry>(json);
Assert.NotNull(res);
Assert.Equal(obj.ChatMessage.Type, res.ChatMessage.Type);
}
}
12 changes: 11 additions & 1 deletion Arrowgene.Ddon.WebServer/DdonWebServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Arrowgene.Ddon.Database;
using Arrowgene.Logging;
using Arrowgene.WebServer;
using Arrowgene.WebServer.Middleware;
using Arrowgene.WebServer.Route;
using Arrowgene.WebServer.Server;
using Arrowgene.WebServer.Server.Kestrel;
Expand Down Expand Up @@ -33,12 +34,21 @@ public DdonWebServer(WebServerSetting setting, IDatabase database)
Logger.Info(servingFile);
}

_webService.AddMiddleware(staticFile);
AddMiddleware(staticFile);

AddRoute(new IndexRoute());
AddRoute(new AccountRoute(database));
}

public void AddMiddleware(IWebMiddleware middleware)
{
_webService.AddMiddleware(middleware);
if (_running)
{
Logger.Info($"Registered new middleware `{middleware.GetType().Name}`");
}
}

public void AddRoute(IWebRoute route)
{
_webService.AddRoute(route);
Expand Down

0 comments on commit ab85b98

Please sign in to comment.