Addendum to login/auth improvements to make sure communication info gets parsed

Only update db fields if they have changed
This commit is contained in:
Ceikry
2022-05-18 13:59:51 +00:00
committed by Ryan
parent 7faa0a2c7c
commit 907838bb5f
7 changed files with 177 additions and 164 deletions
@@ -2,6 +2,8 @@ package core.game.system.communication;
import core.cache.misc.buffer.ByteBufferUtils;
import core.game.node.entity.player.Player;
import org.jetbrains.annotations.NotNull;
import rs09.auth.UserAccountInfo;
import rs09.game.system.SystemLogger;
import core.game.system.monitor.PlayerMonitor;
import core.game.system.mysql.SQLTable;
@@ -18,10 +20,7 @@ import core.net.packet.out.ContactPackets;
import core.tools.StringUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
/**
@@ -153,7 +152,7 @@ public final class CommunicationInfo {
}
}
clanName = ((String) table.getColumn("clanName").getValue());
currentClan = (String) table.getColumn("currentClan").getValue();
currentClan = (String) table.getColumn("currentClan").getValue();
String clanReqs = (String) table.getColumn("clanReqs").getValue();
if (clanReqs == "") {
return;
@@ -168,21 +167,21 @@ public final class CommunicationInfo {
}
rank = ClanRank.values()[ordinal];
switch (i) {
case 0:
joinRequirement = rank;
break;
case 1:
messageRequirement = rank;
break;
case 2:
if (ordinal < 3 || ordinal > 8) {
case 0:
joinRequirement = rank;
break;
case 1:
messageRequirement = rank;
break;
case 2:
if (ordinal < 3 || ordinal > 8) {
break;
}
kickRequirement = rank;
break;
case 3:
lootRequirement = rank;
break;
}
kickRequirement = rank;
break;
case 3:
lootRequirement = rank;
break;
}
}
}
@@ -478,13 +477,13 @@ public final class CommunicationInfo {
return false;
}
switch (target.getSettings().getPrivateChatSetting()) {
case 1:
if (!hasContact(target, player.getName())) {
case 1:
if (!hasContact(target, player.getName())) {
return false;
}
return true;
case 2:
return false;
}
return true;
case 2:
return false;
}
return true;
}
@@ -634,4 +633,54 @@ public final class CommunicationInfo {
public void setLootShare(boolean lootShare) {
this.lootShare = lootShare;
}
public void parse(@NotNull UserAccountInfo accountInfo) {
blocked.addAll(Arrays.asList(accountInfo.getBlocked().split(",")));
clanName = accountInfo.getClanName();
currentClan = accountInfo.getCurrentClan();
String clanReqs = accountInfo.getClanReqs();
String[] tokens = clanReqs.split(",");
ClanRank rank = null;
int ordinal = 0;
for (int i = 0; i < tokens.length; i++) {
ordinal = Integer.parseInt(tokens[i]);
if (ordinal < 0 || ordinal > ClanRank.values().length -1) {
continue;
}
rank = ClanRank.values()[ordinal];
switch (i) {
case 0:
joinRequirement = rank;
break;
case 1:
messageRequirement = rank;
break;
case 2:
if (ordinal < 3 || ordinal > 8) {
break;
}
kickRequirement = rank;
break;
case 3:
lootRequirement = rank;
break;
}
}
String contacts = accountInfo.getContacts();
if (!contacts.isEmpty()) {
String[] datas = contacts.split("~");
Contact contact = null;
for (String d : datas) {
tokens = d.replace("{", "").replace("}", "").split(",");
if (tokens.length == 0) {
continue;
}
contact = new Contact(tokens[0]);
contact.setRank(ClanRank.values()[Integer.parseInt(tokens[1])]);
this.contacts.put(tokens[0], contact);
}
}
}
}
@@ -312,11 +312,11 @@ public final class MSPacketRepository {
switch (response) {
case AlreadyOnline:
player = Repository.getPlayerByName(username);
if (player == null || player.getSession().isActive() || !player.getSession().getAddress().equals(details.getSession().getAddress())) {
/* if (player == null || player.getSession().isActive() || !player.getSession().getAddress().equals(details.getSession().getAddress())) {
details.getSession().write(response, true);
break;
}
player.getPacketDispatch().sendLogout();
}*/
if (player != null) player.getPacketDispatch().sendLogout();
case Success:
if (!details.getSession().isActive()) {
sendPlayerRemoval(username);
@@ -21,10 +21,31 @@ class UserAccountInfo(
) {
companion object {
@JvmStatic fun createDefault() : UserAccountInfo {
return UserAccountInfo("", "", 0, 0, 0, "", "", 0L, 0L, "", "", "", "", "1,0,8,9", 0L, 0L, false)
return UserAccountInfo("", "", 0, 0, 0, "", "", 0L, 0L, "", "", "", "", "1,0,8,9", 0L, 0L, false).also { it.setInitialReferenceValues() }
}
}
lateinit var initialValues: Array<Any>
fun setInitialReferenceValues() {
initialValues = toArray()
}
fun getChangedFields(): Pair<ArrayList<Int>, Array<Any>> {
val current = toArray()
val changed = ArrayList<Int>()
for(i in current.indices) {
if (current[i] != initialValues[i]) changed.add(i)
}
return Pair(changed, current)
}
fun toArray(): Array<Any> {
return arrayOf(username, password, uid, rights, credits, ip, lastUsedIp, muteEndTime, banEndTime, contacts, blocked, clanName, currentClan, clanReqs, timePlayed, lastLogin, online)
}
override fun toString(): String {
return "USER:$username,PASS:$password,UID:$uid,RIGHTS:$rights,CREDITS:$credits,IP:$ip,LASTIP:$lastUsedIp"
}
@@ -14,86 +14,23 @@ import core.net.amsc.MSPacketRepository
import core.net.amsc.ManagementServerState
import core.net.amsc.WorldCommunicator
import rs09.auth.AuthResponse
import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld
import rs09.game.world.GameWorld.loginListeners
import rs09.game.world.repository.Repository
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
/**
* Parses the login of a player.
* @author Emperor
* @author Vexia
*/
class LoginParser(
/**
* The player details file.
*/
val details: PlayerDetails,
/**
* The login type.
*/
private val type: LoginType
) : Runnable {
/**
* Gets the player details.
* @return The player details.
*/
class LoginParser(val details: PlayerDetails, private val type: LoginType) {
/**
* The player in the game, used for reconnect login type.
*/
private var gamePlayer: Player? = null
/**
* Gets the timeStamp.
* @return the timeStamp
*/
/**
* The time stamp.
*/
val timeStamp: Int
override fun run() {
try {
LOCK.tryLock(1000L, TimeUnit.MILLISECONDS)
} catch (e: Exception) {
println(e)
LOCK.unlock()
return
}
try {
if (validateRequest()) {
handleLogin()
}
} catch (t: Throwable) {
t.printStackTrace()
try {
flag(AuthResponse.ErrorLoadingProfile)
Repository.LOGGED_IN_PLAYERS.remove(details.username)
} catch (e: Throwable) {
e.printStackTrace()
}
}
LOCK.unlock()
}
/**
* Handles the actual login.
*/
private fun handleLogin() {
val p = worldInstance
val player = p ?: Player(details)
player.setAttribute("login_type", type)
if (p != null) { // Reconnecting
p.updateDetails(details)
reconnect(p, type)
return
}
initialize(player, false)
}
val timeStamp: Int = GameWorld.ticks
/**
* Initializes the player.
@@ -101,8 +38,9 @@ class LoginParser(
* @param reconnect If the player data should be parsed.
*/
fun initialize(player: Player, reconnect: Boolean) {
if(!validateRequest()) return
if (reconnect) {
reconnect(player, type)
reconnect(player)
return
}
try {
@@ -160,25 +98,12 @@ class LoginParser(
})
}
/**
* Gets the player instance in the current world.
* @return The player instance, if found.
*/
private val worldInstance: Player?
private get() {
var player = Repository.disconnectionQueue[details.username]
if (player == null) {
player = gamePlayer
}
return player
}
/**
* Initializes a reconnecting player.
* @param player The player.
* @param type The login type.
*/
private fun reconnect(player: Player, type: LoginType) {
private fun reconnect(player: Player) {
Repository.disconnectionQueue.remove(details.username)
player.initReconnect()
player.isActive = true
@@ -202,11 +127,6 @@ class LoginParser(
* @return `True` if the request is valid.
*/
private fun validateRequest(): Boolean {
//This is supposed to prevent the double-logging issue. Will it work? Who knows.
if (Repository.LOGGED_IN_PLAYERS.contains(details.username)) {
SystemLogger.logWarn("LOGGED_IN_PLAYERS contains ${details.username}")
return flag(AuthResponse.AlreadyOnline)
}
if (WorldCommunicator.getState() == ManagementServerState.CONNECTING) {
return flag(AuthResponse.LoginServerOffline)
}
@@ -233,20 +153,4 @@ class LoginParser(
details.session.write(response, true)
return response == AuthResponse.Success
}
companion object {
/**
* The lock used to disable 2 of the same player being logged in.
*/
private val LOCK: Lock = ReentrantLock()
}
/**
* Constructs a new `LoginParser` `Object`.
* @param details the player details.
* @param type The login type.
*/
init {
timeStamp = GameWorld.ticks
}
}
@@ -2,6 +2,7 @@ package rs09.net.event
import core.game.node.entity.player.info.ClientInfo
import core.game.node.entity.player.info.PlayerDetails
import core.game.system.communication.CommunicationInfo
import core.net.IoReadEvent
import core.net.IoSession
import rs09.auth.AuthResponse
@@ -31,6 +32,7 @@ class LoginReadEvent(session: IoSession?, buffer: ByteBuffer?) : IoReadEvent(ses
val details = PlayerDetails(info.username)
details.accountInfo = accountInfo
details.communication.parse(accountInfo)
session.clientInfo = ClientInfo(info.displayMode, info.windowMode, info.screenWidth, info.screenHeight)
session.isaacPair = info.isaacPair
@@ -1,6 +1,7 @@
package rs09.storage
import rs09.auth.UserAccountInfo
import rs09.game.system.SystemLogger
import java.lang.Long.max
import java.sql.*
@@ -59,6 +60,7 @@ class SQLStorageProvider : AccountStorageProvider {
result.getLong(16) .let { userData.lastLogin = max(0L, it) }
result.getBoolean(17).let { userData.online = it }
userData.setInitialReferenceValues()
return userData
} else {
return UserAccountInfo.createDefault()
@@ -103,13 +105,17 @@ class SQLStorageProvider : AccountStorageProvider {
if (result.next()) {
info.uid = result.getInt(1)
}
info.setInitialReferenceValues()
}
}
override fun update(info: UserAccountInfo) {
val (updatedFields, rawData) = info.getChangedFields()
val updateQueryString = buildUpdateInfoQuery(updatedFields)
val conn = getConnection()
conn.use {
val compiledUpdateInfoQuery = it.prepareStatement(updateInfoQuery)
val compiledUpdateInfoQuery = it.prepareStatement(updateQueryString)
val emptyInfo = UserAccountInfo.createDefault()
if (info == emptyInfo) {
throw IllegalStateException("Tried to store empty data!")
@@ -122,24 +128,21 @@ class SQLStorageProvider : AccountStorageProvider {
if (info == emptyInfo) {
throw IllegalStateException("Tried to store empty data!")
}
if (updatedFields.isEmpty()) return
compiledUpdateInfoQuery.setString(1, info.username)
compiledUpdateInfoQuery.setString(2, info.password)
compiledUpdateInfoQuery.setInt(3, info.rights)
compiledUpdateInfoQuery.setInt(4, info.credits)
compiledUpdateInfoQuery.setString(5, info.ip)
compiledUpdateInfoQuery.setLong(6, info.muteEndTime)
compiledUpdateInfoQuery.setLong(7, info.banEndTime)
compiledUpdateInfoQuery.setString(8, info.contacts)
compiledUpdateInfoQuery.setString(9, info.blocked)
compiledUpdateInfoQuery.setString(10, info.clanName)
compiledUpdateInfoQuery.setString(11, info.currentClan)
compiledUpdateInfoQuery.setString(12, info.clanReqs)
compiledUpdateInfoQuery.setLong(13, info.timePlayed)
compiledUpdateInfoQuery.setLong(14, info.lastLogin)
compiledUpdateInfoQuery.setBoolean(15, info.online)
compiledUpdateInfoQuery.setInt(16, info.uid)
var fieldIndex = 1
for (updatedFieldIndex in updatedFields) {
when(val data = rawData[updatedFieldIndex]) {
is String -> compiledUpdateInfoQuery.setString(fieldIndex++, data)
is Int -> compiledUpdateInfoQuery.setInt(fieldIndex++, data)
is Boolean -> compiledUpdateInfoQuery.setBoolean(fieldIndex++, data)
is Long -> compiledUpdateInfoQuery.setLong(fieldIndex++, data)
}
}
compiledUpdateInfoQuery.setInt(fieldIndex, info.uid)
compiledUpdateInfoQuery.execute()
info.initialValues = rawData
}
}
@@ -193,22 +196,36 @@ class SQLStorageProvider : AccountStorageProvider {
"online" +
") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
private const val updateInfoQuery = "UPDATE members SET " +
"username = ?," +
"password = ?," +
"rights = ?," +
"credits = ?," +
"lastGameIp = ?," +
"muteTime = ?," +
"banTime = ?," +
"contacts = ?," +
"blocked = ?," +
"clanName = ?," +
"currentClan = ?," +
"clanReqs = ?," +
"timePlayed = ?," +
"lastLogin = ?," +
"online = ?" +
" WHERE uid = ?;"
private fun buildUpdateInfoQuery(updatedIndices: ArrayList<Int>) : String {
val sb = StringBuilder("UPDATE members SET ")
val validIndices = updatedIndices.filter { it in UPDATE_QUERY_FIELDS.keys }
for ((index, updatedIndex) in validIndices.withIndex()) {
sb.append(UPDATE_QUERY_FIELDS[updatedIndex] ?: continue)
sb.append(" = ?")
if (index < validIndices.size - 1)
sb.append(",")
}
sb.append(" WHERE uid = ?;")
return sb.toString()
}
//Maps UserAccountInfo updated field indices to the column name for SQL. Mini ORM I guess?
private val UPDATE_QUERY_FIELDS = mapOf<Int, String>(
0 to "username",
1 to "password",
3 to "rights",
4 to "credits",
6 to "lastGameIp",
7 to "muteTime",
8 to "banTime",
9 to "contacts",
10 to "blocked",
11 to "clanName",
12 to "currentClan",
13 to "clanReqs",
14 to "timePlayed",
15 to "lastLogin",
16 to "online"
)
}
}
@@ -178,4 +178,24 @@ class SQLStorageProviderTests {
val info3 = storage.getAccountInfo("dbupdateacc")
Assertions.assertEquals(2, info3.rights)
}
@Test fun shouldCorrectlyUpdateMultipleChangedValues() {
val userData = UserAccountInfo.createDefault()
userData.username = "borpis2"
userData.password = "test"
testAccountNames.add("borpis2")
storage.store(userData)
val lastLogin = System.currentTimeMillis()
userData.credits = 2
userData.lastLogin = lastLogin
userData.currentClan = "3009scape"
storage.update(userData)
val data = storage.getAccountInfo(userData.username)
Assertions.assertEquals(2, data.credits, "Wrong data: $data")
Assertions.assertEquals(lastLogin, data.lastLogin, "Wrong data: $data")
Assertions.assertEquals("3009scape", data.currentClan, "Wrong data: $data")
}
}