From f30839d297094d615313d48b57b1f57a73b27806 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 15 May 2014 06:28:31 +0200 Subject: [PATCH] Introducing user numbers (not generated from Brightstar entity identifiers anymore) and using partial database class methods instead to keep the code properly managed --- ...BrightstarDatabaseAuthenticationHandler.cs | 34 ++++--- .../Database/BrightstarDatabaseContext.cs | 19 ++-- .../BrightstarDatabaseContext.custom.cs | 96 +++++++++++++++++++ .../Database/DatabaseUserExistsException.cs | 8 ++ src/npserv/Database/IUser.cs | 2 + src/npserv/Program.cs | 36 +++---- src/npserv/npserv.csproj | 2 + 7 files changed, 148 insertions(+), 49 deletions(-) create mode 100644 src/npserv/Database/BrightstarDatabaseContext.custom.cs create mode 100644 src/npserv/Database/DatabaseUserExistsException.cs diff --git a/src/npserv/BrightstarDatabaseAuthenticationHandler.cs b/src/npserv/BrightstarDatabaseAuthenticationHandler.cs index fd623e0..0f3d437 100644 --- a/src/npserv/BrightstarDatabaseAuthenticationHandler.cs +++ b/src/npserv/BrightstarDatabaseAuthenticationHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Globalization; using System.Linq; using log4net; @@ -26,25 +27,28 @@ namespace NPSharp.CommandLine.Server public AuthenticationResult AuthenticateUser(NPServerClient client, string token) { + var ar = new AuthenticationResult(); + // Check if token is valid - var resultEnum = _db.Sessions.Where(s => s.Id == token && s.ExpiryTime > DateTime.Now); - if (!resultEnum.Any()) - return new AuthenticationResult(); // authentication failed because token is invalid - - var session = resultEnum.Single(); - - var ar = - new AuthenticationResult(new CSteamID + _db.ValidateSession(token, session => + { + if (session == null) { - AccountID = uint.Parse(session.User.Id, NumberStyles.Integer), - AccountInstance = 1, - AccountType = EAccountType.Individual, - AccountUniverse = EUniverse.Public - }); + return; + } - _db.DeleteObject(session); + ar = + new AuthenticationResult(new CSteamID + { + AccountID = session.User.UserNumber, + AccountInstance = 1, + AccountType = EAccountType.Individual, + AccountUniverse = EUniverse.Public + }); + + _log.DebugFormat("Deleting validated session {0}", session.Id); + }); _db.SaveChanges(); - _log.DebugFormat("Deleted now used session {0}", session.Id); return ar; } diff --git a/src/npserv/Database/BrightstarDatabaseContext.cs b/src/npserv/Database/BrightstarDatabaseContext.cs index 8a39b69..ae1d7fb 100644 --- a/src/npserv/Database/BrightstarDatabaseContext.cs +++ b/src/npserv/Database/BrightstarDatabaseContext.cs @@ -1,20 +1,11 @@ -// ----------------------------------------------------------------------- -// -// This code was generated from a template. -// -// Changes to this file may cause incorrect behaviour and will be lost -// if the code is regenerated. -// -//------------------------------------------------------------------------ - -using System; +using System; using System.Collections.Generic; using BrightstarDB.Client; using BrightstarDB.EntityFramework; namespace NPSharp.CommandLine.Server.Database { - public class BrightstarDatabaseContext : BrightstarEntityContext + public partial class BrightstarDatabaseContext : BrightstarEntityContext { private static readonly EntityMappingStore TypeMappings; @@ -392,6 +383,12 @@ namespace NPSharp.CommandLine.Server.Database set { SetRelatedProperty("UserMail", value); } } + public UInt32 UserNumber + { + get { return GetRelatedProperty("UserNumber"); } + set { SetRelatedProperty("UserNumber", value); } + } + public String PasswordHash { get { return GetRelatedProperty("PasswordHash"); } diff --git a/src/npserv/Database/BrightstarDatabaseContext.custom.cs b/src/npserv/Database/BrightstarDatabaseContext.custom.cs new file mode 100644 index 0000000..7185cd7 --- /dev/null +++ b/src/npserv/Database/BrightstarDatabaseContext.custom.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; + +namespace NPSharp.CommandLine.Server.Database +{ + public partial class BrightstarDatabaseContext + { + public IUser CreateUser(string name, string email, string password) + { + if (UserExists(name)) + throw new DatabaseUserExistsException(); + + var user = Users.Create(); + user.UserName = name; + user.UserMail = email; + user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(password); + user.UserNumber = _genUserNumber(/*user.Id*/); + + return user; + } + + private uint _genUserNumber(/*string userId*/) + { + /* + // for some reason we sometimes get full URIs here. + userId = userId.Split('/').Last().Replace("-", ""); + + // Since the string is a hexified UNIQUE identifier, + // use the numeric representation of it. + var userNum = uint.Parse(userId, NumberStyles.HexNumber); + */ + + // The above doesn't work since the GUID has a few bits too much :P + // So instead - even though taking more queries - we will use the user + // count to approximate a new user ID. + var userNum = (uint)Users.Count() + 1; + while (Users.Count(u => u.UserNumber == userNum) > 0) + userNum++; + + return userNum; + } + + public bool UserExists(string userName) + { + return GetUser(userName) != null; + } + + public IUser GetUser(string userName) + { + var users = Users.Where(u => u.UserName == userName).ToArray(); + return users.Any() ? users.Single() : null; + } + + /// + /// Creates a user session. + /// + /// The user to assign the session to. + /// The time span in seconds. Default: 3 minutes. + /// The newly created user session + public ISession CreateSession(IUser user, uint validTimeSpan = 3 * 60) + { + var session = Sessions.Create(); + session.ExpiryTime = DateTime.Now + TimeSpan.FromSeconds(validTimeSpan); + + user.Sessions.Add(session); + + return session; + } + + /// + /// Tries to find the wanted session and drops it if it's valid, + /// therefore "using it". + /// + /// The token of the wanted session + /// The callback to use for session results (goes for both invalid and valid sessions) + /// The found session if the session is validated successfully, otherwise null. + public void ValidateSession(string sessionToken, Action callback) + { + var sessions = Sessions + .Where(s => s.Id == sessionToken).ToArray() // database level query + .Where(s => s.ExpiryTime > DateTime.Now).ToArray(); // local level query (seems like this isn't supported [yet]) + + // We have to use a callback here since deleting the object from database + // will also release it from .NET's management and therefore makes the object + // invalid. + if (!sessions.Any()) + callback(null); + else + { + var session = sessions.Single(); + callback(session); + DeleteObject(session); + } + } + } +} diff --git a/src/npserv/Database/DatabaseUserExistsException.cs b/src/npserv/Database/DatabaseUserExistsException.cs new file mode 100644 index 0000000..5f7cea7 --- /dev/null +++ b/src/npserv/Database/DatabaseUserExistsException.cs @@ -0,0 +1,8 @@ +using System; + +namespace NPSharp.CommandLine.Server.Database +{ + class DatabaseUserExistsException : Exception + { + } +} diff --git a/src/npserv/Database/IUser.cs b/src/npserv/Database/IUser.cs index ad2315e..35a0fd0 100644 --- a/src/npserv/Database/IUser.cs +++ b/src/npserv/Database/IUser.cs @@ -13,6 +13,8 @@ namespace NPSharp.CommandLine.Server.Database string UserMail { get; set; } + uint UserNumber { get; set; } + string PasswordHash { get; set; } DateTime LastLogin { get; set; } diff --git a/src/npserv/Program.cs b/src/npserv/Program.cs index 5e8dc34..92f4fc4 100644 --- a/src/npserv/Program.cs +++ b/src/npserv/Program.cs @@ -33,7 +33,6 @@ namespace NPSharp.CommandLine.Server private static BrightstarDatabaseContext OpenDatabase(string store = "NP") { - // TODO: This line is CREATING a new database but it's supposed to open it only if it's already created. Look up! return new BrightstarDatabaseContext( "type=embedded;storesdirectory=Database\\;storename=" + store, @@ -53,21 +52,16 @@ namespace NPSharp.CommandLine.Server if (db.Users.Count() > 0) return; - // Create first user (test:test) - var testUser = db.Users.Create(); - testUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword("test"); - testUser.UserMail = "test@localhost"; - testUser.UserName = "test"; + // Create first user + var testUser = db.CreateUser("test", "test@localhost", "test"); + db.SaveChanges(); _log.InfoFormat( "Created first user with following details:" + Environment.NewLine + Environment.NewLine + - "Username: {0}" + Environment.NewLine + "Password: {1}", + "Username: {0}" + Environment.NewLine + + "Password: {1}" + Environment.NewLine, testUser.UserName, "test"); - - db.SaveChanges(); - - _log.DebugFormat("First user id is {0}", testUser.Id); } // Cleanup thread @@ -78,11 +72,13 @@ namespace NPSharp.CommandLine.Server using (var dbForCleanup = OpenDatabase()) { _log.Debug("Starting cleanup..."); + foreach (var session in dbForCleanup.Sessions.Where(s => s.ExpiryTime < DateTime.Now).ToArray()) { _log.DebugFormat("Session {0} became invalid", session.Id); dbForCleanup.DeleteObject(session); } + foreach (var ban in dbForCleanup.Bans.Where(s => s.ExpiryTime < DateTime.Now).ToArray()) { _log.DebugFormat("Ban {0} became invalid", ban.Id); @@ -95,7 +91,6 @@ namespace NPSharp.CommandLine.Server dbForCleanup.DeleteObject(cheatDetection); } - _log.Debug("Saving cleanup..."); dbForCleanup.SaveChanges(); _log.Debug("Cleanup done."); @@ -117,16 +112,12 @@ namespace NPSharp.CommandLine.Server { using (var db = OpenDatabase()) { - var matchingUsers = - db.Users.Where(u => u.UserName == loginUsername).ToArray() // brightstar level - .Where(u => BCrypt.Net.BCrypt.Verify(loginPassword, u.PasswordHash)).ToArray() // local level - ; + var user = db.GetUser(loginUsername); - if (!matchingUsers.Any()) + + if (user == null || !BCrypt.Net.BCrypt.Verify(loginPassword, user.PasswordHash)) return new SessionAuthenticationResult {Reason = "Invalid credentials"}; - var user = matchingUsers.Single(); - // Check for bans var bans = user.Bans.Where(b => b.ExpiryTime > DateTime.Now).ToArray(); if (bans.Any()) @@ -153,9 +144,8 @@ namespace NPSharp.CommandLine.Server } // Create user session - var session = db.Sessions.Create(); - session.ExpiryTime = DateTime.Now + TimeSpan.FromMinutes(3); - user.Sessions.Add(session); + var session = db.CreateSession(user); + _log.DebugFormat("Created session {0}", session.Id); // Update user's last login data user.LastLogin = DateTime.Now; @@ -168,7 +158,7 @@ namespace NPSharp.CommandLine.Server { Success = true, SessionToken = session.Id, - UserID = uint.Parse(user.Id, NumberStyles.Integer), + UserID = user.UserNumber, UserMail = user.UserMail, UserName = user.UserName }; diff --git a/src/npserv/npserv.csproj b/src/npserv/npserv.csproj index 417427c..afca0e9 100644 --- a/src/npserv/npserv.csproj +++ b/src/npserv/npserv.csproj @@ -74,6 +74,8 @@ + +