mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 03:53:26 -08:00
[Server] Move replay code exchange into the database interface (#7839)
createHashForReplay, cmdReplayGetCode, cmdReplaySubmitCode, cmdReplayDownloadByGameId and cmdGrantReplayAccess still issued their own SQL through Servatrice_DatabaseInterface. Add checkReplayAccess, getReplayName, getReplayHashSource, addReplayAccess, getLatestReplayForGame and grantReplayAccessByName to Server_DatabaseInterface, implement them with the same statements, bind order and response mapping, and let the commands ask the injected interface. Hashing and code parsing stay in the handler; the shared insert is reused by both grant paths.
This commit is contained in:
parent
b2e69d3443
commit
03adbd9787
4 changed files with 181 additions and 103 deletions
|
|
@ -361,6 +361,36 @@ public:
|
||||||
{
|
{
|
||||||
return ReplayWriteResult::Failed;
|
return ReplayWriteResult::Failed;
|
||||||
}
|
}
|
||||||
|
/** @brief Checks whether a user has an access row for a match's replays. */
|
||||||
|
virtual ReplayAccessResult checkReplayAccess(int /* gameId */, int /* userId */)
|
||||||
|
{
|
||||||
|
return ReplayAccessResult::Failed;
|
||||||
|
}
|
||||||
|
/** @brief Reads the stored name of a match's replay access row. */
|
||||||
|
virtual ReplayAccessResult getReplayName(int /* gameId */, QString & /* replayName */)
|
||||||
|
{
|
||||||
|
return ReplayAccessResult::Failed;
|
||||||
|
}
|
||||||
|
/** @brief Copies the leading bytes of a match's replay files, the input of its share code. */
|
||||||
|
virtual bool getReplayHashSource(int /* gameId */, QByteArray & /* source */)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/** @brief Grants one user access to a match's replays. */
|
||||||
|
virtual bool addReplayAccess(int /* gameId */, int /* userId */, const QString & /* replayName */)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/** @brief Fetches the newest replay file of a game for a download-by-game request. */
|
||||||
|
virtual ReplayAccessResult getLatestReplayForGame(int /* gameId */, int & /* replayId */, QByteArray & /* data */)
|
||||||
|
{
|
||||||
|
return ReplayAccessResult::Failed;
|
||||||
|
}
|
||||||
|
/** @brief Grants a user, resolved by name, access to an existing match's replays. */
|
||||||
|
virtual ReplayWriteResult grantReplayAccessByName(int /* gameId */, const QString & /* userName */)
|
||||||
|
{
|
||||||
|
return ReplayWriteResult::Failed;
|
||||||
|
}
|
||||||
/** @brief Append a moderator/admin action to the server audit log. */
|
/** @brief Append a moderator/admin action to the server audit log. */
|
||||||
virtual void addAuditRecord(const QString & /* user */,
|
virtual void addAuditRecord(const QString & /* user */,
|
||||||
const QString & /* ipaddress */,
|
const QString & /* ipaddress */,
|
||||||
|
|
|
||||||
|
|
@ -1939,6 +1939,105 @@ Server_DatabaseInterface::ReplayWriteResult Servatrice_DatabaseInterface::delete
|
||||||
return query->numRowsAffected() > 0 ? ReplayWriteResult::Ok : ReplayWriteResult::NotFound;
|
return query->numRowsAffected() > 0 ? ReplayWriteResult::Ok : ReplayWriteResult::NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Server_DatabaseInterface::ReplayAccessResult Servatrice_DatabaseInterface::checkReplayAccess(int gameId, int userId)
|
||||||
|
{
|
||||||
|
QSqlQuery *query =
|
||||||
|
prepareQuery("select 1 from {prefix}_replays_access where id_game = :id_game and id_player = :id_player");
|
||||||
|
query->bindValue(":id_game", gameId);
|
||||||
|
query->bindValue(":id_player", userId);
|
||||||
|
if (!execSqlQuery(query)) {
|
||||||
|
return ReplayAccessResult::Failed;
|
||||||
|
}
|
||||||
|
if (!query->next()) {
|
||||||
|
return ReplayAccessResult::AccessDenied;
|
||||||
|
}
|
||||||
|
return ReplayAccessResult::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server_DatabaseInterface::ReplayAccessResult Servatrice_DatabaseInterface::getReplayName(int gameId,
|
||||||
|
QString &replayName)
|
||||||
|
{
|
||||||
|
QSqlQuery *query = prepareQuery("select replay_name from {prefix}_replays_access where id_game = :id_game limit 1");
|
||||||
|
query->bindValue(":id_game", gameId);
|
||||||
|
if (!execSqlQuery(query)) {
|
||||||
|
return ReplayAccessResult::Failed;
|
||||||
|
}
|
||||||
|
if (!query->next()) {
|
||||||
|
return ReplayAccessResult::NotFound;
|
||||||
|
}
|
||||||
|
replayName = query->value(0).toString();
|
||||||
|
return ReplayAccessResult::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Servatrice_DatabaseInterface::getReplayHashSource(int gameId, QByteArray &source)
|
||||||
|
{
|
||||||
|
QSqlQuery *query = prepareQuery("select replay from {prefix}_replays where id_game = :id_game limit 3");
|
||||||
|
query->bindValue(":id_game", gameId);
|
||||||
|
if (!execSqlQuery(query)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (query->next()) {
|
||||||
|
QByteArray replay = query->value(0).toByteArray();
|
||||||
|
replay.truncate(128);
|
||||||
|
source.append(replay);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Servatrice_DatabaseInterface::addReplayAccess(int gameId, int userId, const QString &replayName)
|
||||||
|
{
|
||||||
|
QSqlQuery *query =
|
||||||
|
prepareQuery("insert into {prefix}_replays_access (id_game, id_player, replay_name, do_not_hide) "
|
||||||
|
"values(:idgame, :idplayer, :replayname, 0)");
|
||||||
|
query->bindValue(":idgame", gameId);
|
||||||
|
query->bindValue(":idplayer", userId);
|
||||||
|
query->bindValue(":replayname", replayName);
|
||||||
|
|
||||||
|
return execSqlQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server_DatabaseInterface::ReplayAccessResult
|
||||||
|
Servatrice_DatabaseInterface::getLatestReplayForGame(int gameId, int &replayId, QByteArray &data)
|
||||||
|
{
|
||||||
|
QSqlQuery *query = prepareQuery("SELECT r.id, r.replay FROM {prefix}_replays r "
|
||||||
|
"WHERE r.id_game = :game_id ORDER BY r.id DESC LIMIT 1");
|
||||||
|
query->bindValue(":game_id", gameId);
|
||||||
|
if (!execSqlQuery(query)) {
|
||||||
|
return ReplayAccessResult::Failed;
|
||||||
|
}
|
||||||
|
if (!query->next()) {
|
||||||
|
return ReplayAccessResult::NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
replayId = query->value(0).toInt();
|
||||||
|
data = query->value(1).toByteArray();
|
||||||
|
return ReplayAccessResult::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server_DatabaseInterface::ReplayWriteResult
|
||||||
|
Servatrice_DatabaseInterface::grantReplayAccessByName(int gameId, const QString &userName)
|
||||||
|
{
|
||||||
|
QSqlQuery *existsQuery = prepareQuery("select count(*) from {prefix}_replays_access where id_game = :idgame");
|
||||||
|
existsQuery->bindValue(":idgame", gameId);
|
||||||
|
if (!execSqlQuery(existsQuery) || !existsQuery->next()) {
|
||||||
|
return ReplayWriteResult::Failed;
|
||||||
|
}
|
||||||
|
if (existsQuery->value(0).toInt() <= 0) {
|
||||||
|
return ReplayWriteResult::NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery *idQuery = prepareQuery("select id from {prefix}_users WHERE name = :name");
|
||||||
|
idQuery->bindValue(":name", userName);
|
||||||
|
if (!execSqlQuery(idQuery) || !idQuery->next()) {
|
||||||
|
return ReplayWriteResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addReplayAccess(gameId, idQuery->value(0).toInt(), QStringLiteral("Moderator Access Replay Grant"))
|
||||||
|
? ReplayWriteResult::Ok
|
||||||
|
: ReplayWriteResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
bool Servatrice_DatabaseInterface::getDeckFilesInFolder(int folderId, int userId, QList<DeckFileListItem> &files)
|
bool Servatrice_DatabaseInterface::getDeckFilesInFolder(int folderId, int userId, QList<DeckFileListItem> &files)
|
||||||
{
|
{
|
||||||
checkSql();
|
checkSql();
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,12 @@ public:
|
||||||
ReplayAccessResult getReplayData(int replayId, int userId, QByteArray &data) override;
|
ReplayAccessResult getReplayData(int replayId, int userId, QByteArray &data) override;
|
||||||
ReplayWriteResult setReplayDoNotHide(int gameId, int userId, bool doNotHide) override;
|
ReplayWriteResult setReplayDoNotHide(int gameId, int userId, bool doNotHide) override;
|
||||||
ReplayWriteResult deleteReplayAccess(int gameId, int userId) override;
|
ReplayWriteResult deleteReplayAccess(int gameId, int userId) override;
|
||||||
|
ReplayAccessResult checkReplayAccess(int gameId, int userId) override;
|
||||||
|
ReplayAccessResult getReplayName(int gameId, QString &replayName) override;
|
||||||
|
bool getReplayHashSource(int gameId, QByteArray &source) override;
|
||||||
|
bool addReplayAccess(int gameId, int userId, const QString &replayName) override;
|
||||||
|
ReplayAccessResult getLatestReplayForGame(int gameId, int &replayId, QByteArray &data) override;
|
||||||
|
ReplayWriteResult grantReplayAccessByName(int gameId, const QString &userName) override;
|
||||||
/** @brief Deletes all expired share bundles (cascades to their items). */
|
/** @brief Deletes all expired share bundles (cascades to their items). */
|
||||||
void cleanupExpiredDeckShares();
|
void cleanupExpiredDeckShares();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1359,19 +1359,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayDeleteMatch(const
|
||||||
*/
|
*/
|
||||||
QString AbstractServerSocketInterface::createHashForReplay(int gameId)
|
QString AbstractServerSocketInterface::createHashForReplay(int gameId)
|
||||||
{
|
{
|
||||||
QSqlQuery *query =
|
|
||||||
sqlInterface->prepareQuery("select replay from {prefix}_replays where id_game = :id_game limit 3");
|
|
||||||
query->bindValue(":id_game", gameId);
|
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray replaysBytes;
|
QByteArray replaysBytes;
|
||||||
while (query->next()) {
|
if (!databaseInterface->getReplayHashSource(gameId, replaysBytes)) {
|
||||||
QByteArray replay = query->value(0).toByteArray();
|
return "";
|
||||||
replay.truncate(128);
|
|
||||||
replaysBytes.append(replay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hash =
|
auto hash =
|
||||||
|
|
@ -1388,17 +1378,14 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayGetCode(const Com
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that user has access to replay match
|
// Check that user has access to replay match
|
||||||
{
|
switch (databaseInterface->checkReplayAccess(cmd.game_id(), userInfo->id())) {
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery(
|
case Server_DatabaseInterface::ReplayAccessResult::Ok:
|
||||||
"select 1 from {prefix}_replays_access where id_game = :id_game and id_player = :id_player");
|
break;
|
||||||
query->bindValue(":id_game", cmd.game_id());
|
case Server_DatabaseInterface::ReplayAccessResult::AccessDenied:
|
||||||
query->bindValue(":id_player", userInfo->id());
|
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
if (!query->next()) {
|
|
||||||
return Response::RespAccessDenied;
|
return Response::RespAccessDenied;
|
||||||
}
|
case Server_DatabaseInterface::ReplayAccessResult::NotFound:
|
||||||
|
case Server_DatabaseInterface::ReplayAccessResult::Failed:
|
||||||
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString hash = createHashForReplay(cmd.game_id());
|
QString hash = createHashForReplay(cmd.game_id());
|
||||||
|
|
@ -1434,17 +1421,16 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const
|
||||||
QString hash = split[1];
|
QString hash = split[1];
|
||||||
|
|
||||||
// Determine if the replay actually exists (and grab the replay name while at it)
|
// Determine if the replay actually exists (and grab the replay name while at it)
|
||||||
auto *replayExistsQuery =
|
QString replayName;
|
||||||
sqlInterface->prepareQuery("select replay_name from {prefix}_replays_access where id_game = :id_game limit 1");
|
switch (databaseInterface->getReplayName(gameId.toInt(), replayName)) {
|
||||||
replayExistsQuery->bindValue(":id_game", gameId);
|
case Server_DatabaseInterface::ReplayAccessResult::Ok:
|
||||||
if (!sqlInterface->execSqlQuery(replayExistsQuery)) {
|
break;
|
||||||
return Response::RespInternalError;
|
case Server_DatabaseInterface::ReplayAccessResult::NotFound:
|
||||||
|
case Server_DatabaseInterface::ReplayAccessResult::AccessDenied:
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
case Server_DatabaseInterface::ReplayAccessResult::Failed:
|
||||||
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
if (!replayExistsQuery->next()) {
|
|
||||||
return Response::RespNameNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &replayName = replayExistsQuery->value(0).toString();
|
|
||||||
|
|
||||||
// Check if hash is correct
|
// Check if hash is correct
|
||||||
if (hash != createHashForReplay(gameId.toInt())) {
|
if (hash != createHashForReplay(gameId.toInt())) {
|
||||||
|
|
@ -1452,26 +1438,18 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if user already has access to replay
|
// Determine if user already has access to replay
|
||||||
auto *alreadyAccessQuery = sqlInterface->prepareQuery(
|
switch (databaseInterface->checkReplayAccess(gameId.toInt(), userInfo->id())) {
|
||||||
"select 1 from {prefix}_replays_access where id_game = :id_game and id_player = :id_player");
|
case Server_DatabaseInterface::ReplayAccessResult::Ok:
|
||||||
alreadyAccessQuery->bindValue(":id_game", gameId);
|
return Response::RespOk;
|
||||||
alreadyAccessQuery->bindValue(":id_player", userInfo->id());
|
case Server_DatabaseInterface::ReplayAccessResult::AccessDenied:
|
||||||
if (!sqlInterface->execSqlQuery(alreadyAccessQuery)) {
|
break;
|
||||||
return Response::RespInternalError;
|
case Server_DatabaseInterface::ReplayAccessResult::NotFound:
|
||||||
}
|
case Server_DatabaseInterface::ReplayAccessResult::Failed:
|
||||||
if (alreadyAccessQuery->next()) {
|
return Response::RespInternalError;
|
||||||
return Response::RespOk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grant the User access to the replay
|
// Grant the User access to the replay
|
||||||
auto *grantReplayAccessQuery =
|
if (!databaseInterface->addReplayAccess(gameId.toInt(), userInfo->id(), replayName)) {
|
||||||
sqlInterface->prepareQuery("insert into {prefix}_replays_access (id_game, id_player, replay_name, do_not_hide) "
|
|
||||||
"values(:idgame, :idplayer, :replayname, 0)");
|
|
||||||
grantReplayAccessQuery->bindValue(":idgame", gameId);
|
|
||||||
grantReplayAccessQuery->bindValue(":idplayer", userInfo->id());
|
|
||||||
grantReplayAccessQuery->bindValue(":replayname", replayName);
|
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(grantReplayAccessQuery)) {
|
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3043,25 +3021,22 @@ AbstractServerSocketInterface::cmdReplayDownloadByGameId(const Command_ReplayDow
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sqlInterface->checkSql()) {
|
if (!databaseInterface->checkSql()) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery("SELECT r.id, r.replay FROM {prefix}_replays r "
|
int replayId = 0;
|
||||||
"WHERE r.id_game = :game_id ORDER BY r.id DESC LIMIT 1");
|
QByteArray data;
|
||||||
query->bindValue(":game_id", cmd.game_id());
|
switch (databaseInterface->getLatestReplayForGame(cmd.game_id(), replayId, data)) {
|
||||||
|
case Server_DatabaseInterface::ReplayAccessResult::Ok:
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
break;
|
||||||
return Response::RespInternalError;
|
case Server_DatabaseInterface::ReplayAccessResult::NotFound:
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
case Server_DatabaseInterface::ReplayAccessResult::AccessDenied:
|
||||||
|
case Server_DatabaseInterface::ReplayAccessResult::Failed:
|
||||||
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!query->next()) {
|
|
||||||
return Response::RespNameNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
int replayId = query->value(0).toInt();
|
|
||||||
QByteArray data = query->value(1).toByteArray();
|
|
||||||
|
|
||||||
Response_ReplayDownloadByGameId *re = new Response_ReplayDownloadByGameId;
|
Response_ReplayDownloadByGameId *re = new Response_ReplayDownloadByGameId;
|
||||||
re->set_replay_data(data.data(), data.size());
|
re->set_replay_data(data.data(), data.size());
|
||||||
re->set_replay_id(replayId);
|
re->set_replay_id(replayId);
|
||||||
|
|
@ -4153,47 +4128,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAdjustMod(const Command
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdGrantReplayAccess(const Command_GrantReplayAccess &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdGrantReplayAccess(const Command_GrantReplayAccess &cmd,
|
||||||
ResponseContainer & /*rc*/)
|
ResponseContainer & /*rc*/)
|
||||||
{
|
{
|
||||||
// Determine if the replay actually exists already
|
switch (databaseInterface->grantReplayAccessByName(cmd.replay_id(), QString::fromStdString(cmd.moderator_name()))) {
|
||||||
auto *replayExistsQuery =
|
case Server_DatabaseInterface::ReplayWriteResult::Ok:
|
||||||
sqlInterface->prepareQuery("select count(*) from {prefix}_replays_access where id_game = :idgame");
|
return Response::RespOk;
|
||||||
replayExistsQuery->bindValue(":idgame", cmd.replay_id());
|
case Server_DatabaseInterface::ReplayWriteResult::NotFound:
|
||||||
if (!sqlInterface->execSqlQuery(replayExistsQuery)) {
|
return Response::RespContextError;
|
||||||
return Response::RespInternalError;
|
case Server_DatabaseInterface::ReplayWriteResult::Failed:
|
||||||
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
if (!replayExistsQuery->next()) {
|
return Response::RespInternalError;
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &replayExists = replayExistsQuery->value(0).toInt() > 0;
|
|
||||||
if (!replayExists) {
|
|
||||||
return Response::RespContextError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the Moderator's User ID (As it's not apart of client, only username is)
|
|
||||||
auto *getModeratorUserIdQuery = sqlInterface->prepareQuery("select id from {prefix}_users WHERE name = :name");
|
|
||||||
getModeratorUserIdQuery->bindValue(":name", QString::fromStdString(cmd.moderator_name()));
|
|
||||||
if (!sqlInterface->execSqlQuery(getModeratorUserIdQuery)) {
|
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
if (!getModeratorUserIdQuery->next()) {
|
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &moderator_id = getModeratorUserIdQuery->value(0).toString();
|
|
||||||
|
|
||||||
// Grant the Moderator access to the replay
|
|
||||||
auto *grantReplayAccessQuery =
|
|
||||||
sqlInterface->prepareQuery("insert into {prefix}_replays_access (id_game, id_player, replay_name, do_not_hide) "
|
|
||||||
"values(:idgame, :idplayer, :replayname, 0)");
|
|
||||||
grantReplayAccessQuery->bindValue(":idgame", cmd.replay_id());
|
|
||||||
grantReplayAccessQuery->bindValue(":idplayer", moderator_id);
|
|
||||||
grantReplayAccessQuery->bindValue(":replayname", "Moderator Access Replay Grant");
|
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(grantReplayAccessQuery)) {
|
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response::RespOk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdForceActivateUser(const Command_ForceActivateUser &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdForceActivateUser(const Command_ForceActivateUser &cmd,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue