Server Config Whitelist Email Providers (#4416)
* Support registration domain whitelist (registration/emailproviderwhitelist) that, if set, will require a user to have an email with one of the specified domain providers. Will require client updates to see the Whitelist message, otherwise they'll be greeted with a default alert. This also works to remove the pain of Google Email addresses and their infinite combination of usernames for the same account (i.e. remove periods and everything after the first plus sign). * Make blacklist response show custom dialog
This commit is contained in:
parent
c0bd49cf13
commit
051be37419
7 changed files with 104 additions and 34 deletions
|
@ -510,9 +510,11 @@ void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quin
|
||||||
tr("It's mandatory to specify a valid email address when registering."));
|
tr("It's mandatory to specify a valid email address when registering."));
|
||||||
break;
|
break;
|
||||||
case Response::RespEmailBlackListed:
|
case Response::RespEmailBlackListed:
|
||||||
QMessageBox::critical(
|
if (reasonStr.isEmpty()) {
|
||||||
this, tr("Registration denied"),
|
reasonStr =
|
||||||
tr("The email address provider used during registration has been blacklisted for use on this server."));
|
"The email address provider used during registration has been blocked from use on this server.";
|
||||||
|
}
|
||||||
|
QMessageBox::critical(this, tr("Registration denied"), reasonStr);
|
||||||
break;
|
break;
|
||||||
case Response::RespTooManyRequests:
|
case Response::RespTooManyRequests:
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
|
|
|
@ -37,11 +37,11 @@ message Response {
|
||||||
RespActivationAccepted = 31; // Server accepted a reg user activation token
|
RespActivationAccepted = 31; // Server accepted a reg user activation token
|
||||||
RespActivationFailed = 32; // Server didn't accept a reg user activation token
|
RespActivationFailed = 32; // Server didn't accept a reg user activation token
|
||||||
RespRegistrationAcceptedNeedsActivation =
|
RespRegistrationAcceptedNeedsActivation =
|
||||||
33; // Server accepted cient registration, but it will need token activation
|
33; // Server accepted client registration, but it will need token activation
|
||||||
RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access
|
RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access
|
||||||
RespClientUpdateRequired = 35; // Client is missing features that the server is requiring
|
RespClientUpdateRequired = 35; // Client is missing features that the server is requiring
|
||||||
RespServerFull = 36; // Server user limit reached
|
RespServerFull = 36; // Server user limit reached
|
||||||
RespEmailBlackListed = 37; // Server has blacklisted the email address provided for registration
|
RespEmailBlackListed = 37; // Server has blocked the email address provided for registration for some reason
|
||||||
}
|
}
|
||||||
enum ResponseType {
|
enum ResponseType {
|
||||||
JOIN_ROOM = 1000;
|
JOIN_ROOM = 1000;
|
||||||
|
|
|
@ -164,6 +164,14 @@ minpasswordlength = 6
|
||||||
; Example: "10minutemail.com,gmail.com"
|
; Example: "10minutemail.com,gmail.com"
|
||||||
;emailproviderblacklist=""
|
;emailproviderblacklist=""
|
||||||
|
|
||||||
|
; You can require users to only use certain email domains for registration. This setting is a
|
||||||
|
; comma-separated list of email provider domains that you have explicitly audited and require
|
||||||
|
; the use of in order to create an account. Comparison's are explicit, so you must specify the
|
||||||
|
; domain in completion, such as gmail.com and hotmail.com. Email whitelist is checked before
|
||||||
|
; Email blacklist is checked, so an email cannot be in both setting configurations.
|
||||||
|
; Example: "gmail.com,hotmail.com,icloud.com"
|
||||||
|
;emailproviderwhitelist=""
|
||||||
|
|
||||||
[forgotpassword]
|
[forgotpassword]
|
||||||
|
|
||||||
; Servatrice can process reset password requests allowing users to reset their account
|
; Servatrice can process reset password requests allowing users to reset their account
|
||||||
|
|
|
@ -267,10 +267,13 @@ bool Servatrice::initServer()
|
||||||
if (getRegistrationEnabled()) {
|
if (getRegistrationEnabled()) {
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
QStringList emailBlackListFilters = getEmailBlackList().split(",", Qt::SkipEmptyParts);
|
QStringList emailBlackListFilters = getEmailBlackList().split(",", Qt::SkipEmptyParts);
|
||||||
|
QStringList emailWhiteListFilters = getEmailWhiteList().split(",", Qt::SkipEmptyParts);
|
||||||
#else
|
#else
|
||||||
QStringList emailBlackListFilters = getEmailBlackList().split(",", QString::SkipEmptyParts);
|
QStringList emailBlackListFilters = getEmailBlackList().split(",", QString::SkipEmptyParts);
|
||||||
|
QStringList emailWhiteListFilters = getEmailWhiteList().split(",", QString::SkipEmptyParts);
|
||||||
#endif
|
#endif
|
||||||
qDebug() << "Email blacklist: " << emailBlackListFilters;
|
qDebug() << "Email blacklist: " << emailBlackListFilters;
|
||||||
|
qDebug() << "Email whitelist: " << emailWhiteListFilters;
|
||||||
qDebug() << "Require email address to register: " << getRequireEmailForRegistrationEnabled();
|
qDebug() << "Require email address to register: " << getRequireEmailForRegistrationEnabled();
|
||||||
qDebug() << "Require email activation via token: " << getRequireEmailActivationEnabled();
|
qDebug() << "Require email activation via token: " << getRequireEmailActivationEnabled();
|
||||||
if (getMaxAccountsPerEmail()) {
|
if (getMaxAccountsPerEmail()) {
|
||||||
|
@ -1074,6 +1077,11 @@ QString Servatrice::getEmailBlackList() const
|
||||||
return settingsCache->value("registration/emailproviderblacklist").toString();
|
return settingsCache->value("registration/emailproviderblacklist").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Servatrice::getEmailWhiteList() const
|
||||||
|
{
|
||||||
|
return settingsCache->value("registration/emailproviderwhitelist").toString();
|
||||||
|
}
|
||||||
|
|
||||||
bool Servatrice::getEnableAudit() const
|
bool Servatrice::getEnableAudit() const
|
||||||
{
|
{
|
||||||
return settingsCache->value("audit/enable_audit", true).toBool();
|
return settingsCache->value("audit/enable_audit", true).toBool();
|
||||||
|
|
|
@ -231,6 +231,7 @@ public:
|
||||||
return dbPrefix;
|
return dbPrefix;
|
||||||
}
|
}
|
||||||
QString getEmailBlackList() const;
|
QString getEmailBlackList() const;
|
||||||
|
QString getEmailWhiteList() const;
|
||||||
AuthenticationMethod getAuthenticationMethod() const
|
AuthenticationMethod getAuthenticationMethod() const
|
||||||
{
|
{
|
||||||
return authenticationMethod;
|
return authenticationMethod;
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -919,14 +920,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userName.isEmpty() && address.isEmpty() && (!clientID.isEmpty())) {
|
if (userName.isEmpty() && address.isEmpty() && (!clientID.isEmpty())) {
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery("select name from {prefix}_users where clientid = :client_id");
|
QSqlQuery *clientIdQuery =
|
||||||
query->bindValue(":client_id", QString::fromStdString(cmd.clientid()));
|
sqlInterface->prepareQuery("select name from {prefix}_users where clientid = :client_id");
|
||||||
sqlInterface->execSqlQuery(query);
|
clientIdQuery->bindValue(":client_id", QString::fromStdString(cmd.clientid()));
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
sqlInterface->execSqlQuery(clientIdQuery);
|
||||||
|
if (!sqlInterface->execSqlQuery(clientIdQuery)) {
|
||||||
qDebug("ClientID username ban lookup failed: SQL Error");
|
qDebug("ClientID username ban lookup failed: SQL Error");
|
||||||
} else {
|
} else {
|
||||||
while (query->next()) {
|
while (clientIdQuery->next()) {
|
||||||
userName = query->value(0).toString();
|
userName = clientIdQuery->value(0).toString();
|
||||||
AbstractServerSocketInterface *user =
|
AbstractServerSocketInterface *user =
|
||||||
static_cast<AbstractServerSocketInterface *>(server->getUsers().value(userName));
|
static_cast<AbstractServerSocketInterface *>(server->getUsers().value(userName));
|
||||||
if (user && !userList.contains(user))
|
if (user && !userList.contains(user))
|
||||||
|
@ -971,6 +973,43 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString AbstractServerSocketInterface::parseEmailAddress(const std::string &stdEmailAddress)
|
||||||
|
{
|
||||||
|
QString emailAddress = QString::fromStdString(stdEmailAddress);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString capturedEmailAddressDomain = match.captured(2);
|
||||||
|
|
||||||
|
// Trim out dots and pluses from Google/Gmail domains
|
||||||
|
if (capturedEmailAddressDomain.toLower() == "gmail.com" ||
|
||||||
|
capturedEmailAddressDomain.toLower() == "googlemail.com") {
|
||||||
|
QString capturedEmailUser = match.captured(1);
|
||||||
|
|
||||||
|
// 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(".", "");
|
||||||
|
|
||||||
|
emailAddress = capturedEmailUser + "@" + capturedEmailAddressDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd,
|
||||||
ResponseContainer &rc)
|
ResponseContainer &rc)
|
||||||
{
|
{
|
||||||
|
@ -988,34 +1027,46 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C
|
||||||
return Response::RespRegistrationDisabled;
|
return Response::RespRegistrationDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString emailBlackList = servatrice->getEmailBlackList();
|
const QString emailBlackList = servatrice->getEmailBlackList();
|
||||||
QString emailAddress = QString::fromStdString(cmd.email());
|
const QString emailWhiteList = servatrice->getEmailWhiteList();
|
||||||
|
const QString emailAddress = parseEmailAddress(cmd.email());
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts);
|
const QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts);
|
||||||
|
const QStringList emailWhiteListFilters = emailWhiteList.split(",", Qt::SkipEmptyParts);
|
||||||
#else
|
#else
|
||||||
QStringList emailBlackListFilters = emailBlackList.split(",", QString::SkipEmptyParts);
|
const QStringList emailBlackListFilters = emailBlackList.split(",", QString::SkipEmptyParts);
|
||||||
|
const QStringList emailWhiteListFilters = emailWhiteList.split(",", QString::SkipEmptyParts);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// verify that users email/provider is not blacklisted
|
bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool();
|
||||||
if (!emailBlackList.trimmed().isEmpty()) {
|
if (requireEmailForRegistration && emailAddress.isEmpty()) {
|
||||||
foreach (QString blackListEmailAddress, emailBlackListFilters) {
|
return Response::RespEmailRequiredToRegister;
|
||||||
if (emailAddress.contains(blackListEmailAddress, Qt::CaseInsensitive)) {
|
|
||||||
if (servatrice->getEnableRegistrationAudit())
|
|
||||||
sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(),
|
|
||||||
this->getAddress(),
|
|
||||||
QString::fromStdString(cmd.clientid()).simplified(),
|
|
||||||
"REGISTER_ACCOUNT", "Email used is blacklisted", false);
|
|
||||||
|
|
||||||
return Response::RespEmailBlackListed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool();
|
const auto emailAddressDomain = emailAddress.split("@").at(1);
|
||||||
if (requireEmailForRegistration) {
|
|
||||||
QRegExp rx("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}\\b");
|
// If a whitelist exists, ensure the email address domain IS in the whitelist
|
||||||
if (emailAddress.isEmpty() || !rx.exactMatch(emailAddress))
|
if (!emailWhiteListFilters.isEmpty() && !emailWhiteListFilters.contains(emailAddressDomain, Qt::CaseInsensitive)) {
|
||||||
return Response::RespEmailRequiredToRegister;
|
if (servatrice->getEnableRegistrationAudit()) {
|
||||||
|
sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(), this->getAddress(),
|
||||||
|
QString::fromStdString(cmd.clientid()).simplified(), "REGISTER_ACCOUNT",
|
||||||
|
"Email used is not whitelisted", false);
|
||||||
|
}
|
||||||
|
auto *re = new Response_Register;
|
||||||
|
re->set_denied_reason_str(
|
||||||
|
"The email address provider used during registration has not been approved for use on this server.");
|
||||||
|
rc.setResponseExtension(re);
|
||||||
|
return Response::RespEmailBlackListed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a blacklist exists, ensure the email address domain is NOT in the blacklist
|
||||||
|
if (!emailBlackListFilters.isEmpty() && emailBlackListFilters.contains(emailAddressDomain, Qt::CaseInsensitive)) {
|
||||||
|
if (servatrice->getEnableRegistrationAudit())
|
||||||
|
sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(), this->getAddress(),
|
||||||
|
QString::fromStdString(cmd.clientid()).simplified(), "REGISTER_ACCOUNT",
|
||||||
|
"Email used is blacklisted", false);
|
||||||
|
|
||||||
|
return Response::RespEmailBlackListed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this method outside of the db interface
|
// TODO: Move this method outside of the db interface
|
||||||
|
@ -1128,7 +1179,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C
|
||||||
return Response::RespRegistrationAccepted;
|
return Response::RespRegistrationAccepted;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (servatrice->getEnableRegistrationAudit())
|
if (servatrice->getEnableRegistrationAudit())
|
||||||
sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(), this->getAddress(),
|
sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(), this->getAddress(),
|
||||||
QString::fromStdString(cmd.clientid()).simplified(), "REGISTER_ACCOUNT",
|
QString::fromStdString(cmd.clientid()).simplified(), "REGISTER_ACCOUNT",
|
||||||
|
|
|
@ -125,6 +125,7 @@ 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 QString parseEmailAddress(const std::string &stdEmailAddress);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AbstractServerSocketInterface(Servatrice *_server,
|
AbstractServerSocketInterface(Servatrice *_server,
|
||||||
|
|
Loading…
Reference in a new issue