Fix #4903: Parse Email Addresses whenever used (#4932)

This commit is contained in:
Zach H 2023-12-09 00:52:47 -05:00 committed by GitHub
parent b73ef58567
commit 07a8cd0a5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 52 deletions

View file

@ -5,6 +5,7 @@
project(Servatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") project(Servatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(servatrice_SOURCES set(servatrice_SOURCES
src/email_parser.cpp
src/main.cpp src/main.cpp
src/servatrice.cpp src/servatrice.cpp
src/servatrice_connection_pool.cpp src/servatrice_connection_pool.cpp

View file

@ -0,0 +1,54 @@
#include "email_parser.h"
#include <QRegularExpression>
#include <QString>
QPair<QString, QString> EmailParser::parseEmailAddress(const QString &dirtyEmailAddress)
{
// https://www.regular-expressions.info/email.html
static const QRegularExpression emailRegex(R"(^([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})$)",
QRegularExpression::CaseInsensitiveOption);
const auto match = emailRegex.match(dirtyEmailAddress);
if (dirtyEmailAddress.isEmpty() || !match.hasMatch()) {
return {};
}
QString capturedEmailUser = match.captured(1);
QString capturedEmailAddressDomain = match.captured(2);
// Replace googlemail.com with gmail.com, as is standard nowadays
// https://www.gmass.co/blog/domains-gmail-com-googlemail-com-and-google-com/
if (capturedEmailAddressDomain.toLower() == "googlemail.com") {
capturedEmailAddressDomain = "gmail.com";
}
// Trim out dots and pluses from Google/Gmail domains
if (capturedEmailAddressDomain.toLower() == "gmail.com") {
// Remove all content after first plus sign (as unnecessary with gmail)
// https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html
const auto firstPlusSign = capturedEmailUser.indexOf("+");
if (firstPlusSign != -1) {
capturedEmailUser = capturedEmailUser.left(firstPlusSign);
}
// Remove all periods (as unnecessary with gmail)
// https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html
capturedEmailUser.replace(".", "");
}
return {capturedEmailUser, capturedEmailAddressDomain};
}
QString EmailParser::getParsedEmailAddress(const QString &dirtyEmailAddress)
{
const auto parsedEmailAddress = EmailParser::parseEmailAddress(dirtyEmailAddress);
return EmailParser::getParsedEmailAddress(parsedEmailAddress);
}
QString EmailParser::getParsedEmailAddress(const QPair<QString, QString> &emailAddressIntermediate)
{
const auto emailUser = emailAddressIntermediate.first;
const auto emailDomain = emailAddressIntermediate.second;
return emailUser + "@" + emailDomain;
}

View file

@ -0,0 +1,15 @@
#ifndef COCKATRICE_EMAILPARSER_H
#define COCKATRICE_EMAILPARSER_H
#include <QPair>
#include <QString>
class EmailParser
{
public:
static QPair<QString, QString> parseEmailAddress(const QString &dirtyEmailAddress);
static QString getParsedEmailAddress(const QString &dirtyEmailAddress);
static QString getParsedEmailAddress(const QPair<QString, QString> &emailAddressIntermediate);
};
#endif // COCKATRICE_EMAILPARSER_H

View file

@ -20,6 +20,7 @@
#include "servatrice.h" #include "servatrice.h"
#include "decklist.h" #include "decklist.h"
#include "email_parser.h"
#include "featureset.h" #include "featureset.h"
#include "isl_interface.h" #include "isl_interface.h"
#include "main.h" #include "main.h"
@ -627,7 +628,7 @@ void Servatrice::statusUpdate()
while (servDbSelQuery->next()) { while (servDbSelQuery->next()) {
const QString userName = servDbSelQuery->value(0).toString(); const QString userName = servDbSelQuery->value(0).toString();
const QString emailAddress = servDbSelQuery->value(1).toString(); const auto emailAddress = EmailParser::getParsedEmailAddress(servDbSelQuery->value(1).toString());
const QString token = servDbSelQuery->value(2).toString(); const QString token = servDbSelQuery->value(2).toString();
if (smtpClient->enqueueActivationTokenMail(userName, emailAddress, token)) { if (smtpClient->enqueueActivationTokenMail(userName, emailAddress, token)) {
@ -649,7 +650,7 @@ void Servatrice::statusUpdate()
while (forgotPwQuery->next()) { while (forgotPwQuery->next()) {
const QString userName = forgotPwQuery->value(0).toString(); const QString userName = forgotPwQuery->value(0).toString();
const QString emailAddress = forgotPwQuery->value(1).toString(); const auto emailAddress = EmailParser::getParsedEmailAddress(forgotPwQuery->value(1).toString());
const QString token = forgotPwQuery->value(2).toString(); const QString token = forgotPwQuery->value(2).toString();
if (smtpClient->enqueueForgotPasswordTokenMail(userName, emailAddress, token)) { if (smtpClient->enqueueForgotPasswordTokenMail(userName, emailAddress, token)) {

View file

@ -21,6 +21,7 @@
#include "serversocketinterface.h" #include "serversocketinterface.h"
#include "decklist.h" #include "decklist.h"
#include "email_parser.h"
#include "main.h" #include "main.h"
#include "pb/command_deck_del.pb.h" #include "pb/command_deck_del.pb.h"
#include "pb/command_deck_del_dir.pb.h" #include "pb/command_deck_del_dir.pb.h"
@ -1004,43 +1005,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com
return Response::RespOk; return Response::RespOk;
} }
QPair<QString, QString> AbstractServerSocketInterface::parseEmailAddress(const QString &emailAddress)
{
// https://www.regular-expressions.info/email.html
static const QRegularExpression emailRegex(R"(^([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})$)",
QRegularExpression::CaseInsensitiveOption);
const auto match = emailRegex.match(emailAddress);
if (emailAddress.isEmpty() || !match.hasMatch()) {
return {};
}
QString capturedEmailUser = match.captured(1);
QString capturedEmailAddressDomain = match.captured(2);
// Replace googlemail.com with gmail.com, as is standard nowadays
// https://www.gmass.co/blog/domains-gmail-com-googlemail-com-and-google-com/
if (capturedEmailAddressDomain.toLower() == "googlemail.com") {
capturedEmailAddressDomain = "gmail.com";
}
// Trim out dots and pluses from Google/Gmail domains
if (capturedEmailAddressDomain.toLower() == "gmail.com") {
// Remove all content after first plus sign (as unnecessary with gmail)
// https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html
const int firstPlusSign = capturedEmailUser.indexOf("+");
if (firstPlusSign != -1) {
capturedEmailUser = capturedEmailUser.left(firstPlusSign);
}
// Remove all periods (as unnecessary with gmail)
// https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html
capturedEmailUser.replace(".", "");
}
return {capturedEmailUser, capturedEmailAddressDomain};
}
Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd, Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd,
ResponseContainer &rc) ResponseContainer &rc)
{ {
@ -1059,9 +1023,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C
const QString emailBlackList = servatrice->getEmailBlackList(); const QString emailBlackList = servatrice->getEmailBlackList();
const QString emailWhiteList = servatrice->getEmailWhiteList(); const QString emailWhiteList = servatrice->getEmailWhiteList();
auto parsedEmailAddress = parseEmailAddress(nameFromStdString(cmd.email())); const auto parsedEmailParts = EmailParser::parseEmailAddress(nameFromStdString(cmd.email()));
const QString emailUser = parsedEmailAddress.first; const auto emailUser = parsedEmailParts.first;
const QString emailDomain = parsedEmailAddress.second; const auto emailDomain = parsedEmailParts.second;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
const QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts); const QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts);
const QStringList emailWhiteListFilters = emailWhiteList.split(",", Qt::SkipEmptyParts); const QStringList emailWhiteListFilters = emailWhiteList.split(",", Qt::SkipEmptyParts);
@ -1126,9 +1090,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C
return Response::RespUserAlreadyExists; return Response::RespUserAlreadyExists;
} }
QString emailAddress = emailUser + "@" + emailDomain; const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(parsedEmailParts);
if (servatrice->getMaxAccountsPerEmail() > 0 && if (servatrice->getMaxAccountsPerEmail() > 0 &&
sqlInterface->checkNumberOfUserAccounts(emailAddress) >= servatrice->getMaxAccountsPerEmail()) { sqlInterface->checkNumberOfUserAccounts(parsedEmailAddress) >= servatrice->getMaxAccountsPerEmail()) {
if (servatrice->getEnableRegistrationAudit()) if (servatrice->getEnableRegistrationAudit())
sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(),
"REGISTER_ACCOUNT", "Too many usernames registered with this email address", "REGISTER_ACCOUNT", "Too many usernames registered with this email address",
@ -1184,7 +1148,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C
} }
bool requireEmailActivation = settingsCache->value("registration/requireemailactivation", true).toBool(); bool requireEmailActivation = settingsCache->value("registration/requireemailactivation", true).toBool();
bool regSucceeded = sqlInterface->registerUser(userName, realName, password, passwordNeedsHash, emailAddress, bool regSucceeded = sqlInterface->registerUser(userName, realName, password, passwordNeedsHash, parsedEmailAddress,
country, !requireEmailActivation); country, !requireEmailActivation);
if (regSucceeded) { if (regSucceeded) {
@ -1262,7 +1226,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma
return Response::RespFunctionNotAllowed; return Response::RespFunctionNotAllowed;
QString realName = nameFromStdString(cmd.real_name()); QString realName = nameFromStdString(cmd.real_name());
QString emailAddress = nameFromStdString(cmd.email()); const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email()));
QString country = nameFromStdString(cmd.country()); QString country = nameFromStdString(cmd.country());
bool checkedPassword = false; bool checkedPassword = false;
@ -1310,8 +1274,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma
query->bindValue(":realName", _realName); query->bindValue(":realName", _realName);
} }
if (cmd.has_email()) { if (cmd.has_email()) {
auto _emailAddress = nameFromStdString(cmd.email()); const auto _parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email()));
query->bindValue(":email", _emailAddress); query->bindValue(":email", _parsedEmailAddress);
} }
if (cmd.has_country()) { if (cmd.has_country()) {
auto _country = nameFromStdString(cmd.country()); auto _country = nameFromStdString(cmd.country());
@ -1326,7 +1290,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma
userInfo->set_real_name(realName.toStdString()); userInfo->set_real_name(realName.toStdString());
} }
if (cmd.has_email()) { if (cmd.has_email()) {
userInfo->set_email(emailAddress.toStdString()); userInfo->set_email(parsedEmailAddress.toStdString());
} }
if (cmd.has_country()) { if (cmd.has_country()) {
userInfo->set_country(country.toStdString()); userInfo->set_country(country.toStdString());
@ -1543,8 +1507,8 @@ AbstractServerSocketInterface::cmdForgotPasswordChallenge(const Command_ForgotPa
return Response::RespFunctionNotAllowed; return Response::RespFunctionNotAllowed;
} }
if (!sqlInterface->validateTableColumnStringData("{prefix}_users", "email", userName, const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email()));
nameFromStdString(cmd.email()))) { if (!sqlInterface->validateTableColumnStringData("{prefix}_users", "email", userName, parsedEmailAddress)) {
if (servatrice->getEnableForgotPasswordAudit()) { if (servatrice->getEnableForgotPasswordAudit()) {
sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(),
"PASSWORD_RESET_CHALLENGE", "Failed to answer email challenge question", "PASSWORD_RESET_CHALLENGE", "Failed to answer email challenge question",

View file

@ -131,7 +131,6 @@ private:
bool removeAdminFlagFromUser(const QString &user, int flag); bool removeAdminFlagFromUser(const QString &user, int flag);
bool isPasswordLongEnough(const int passwordLength); bool isPasswordLongEnough(const int passwordLength);
static QPair<QString, QString> parseEmailAddress(const QString &emailAddress);
void removeSaidMessages(const QString &userName, int amount); void removeSaidMessages(const QString &userName, int amount);
public: public: