diff --git a/com.woltlab.wcf/option.xml b/com.woltlab.wcf/option.xml
index 2a82335759e..3fdd560cbe5 100644
--- a/com.woltlab.wcf/option.xml
+++ b/com.woltlab.wcf/option.xml
@@ -166,17 +166,6 @@
security.general
15
-
- security
-
-
- security.blacklist
- 1
-
-
- security.blacklist
- 2
-
security
@@ -186,6 +175,9 @@
security.antispam
+
+ security.antispam
+
security
@@ -660,13 +652,6 @@ moreThanOnce:wcf.acp.option.blacklist_sfs_enable.moreThanOnce
simpleMatch:wcf.acp.option.blacklist_sfs_enable.simpleMatch]]>
90percentile
-
+
+
+
+
diff --git a/constants.php b/constants.php
index 1bb51d0e497..a0aedad769b 100644
--- a/constants.php
+++ b/constants.php
@@ -117,6 +117,7 @@
\define('REGISTER_USERNAME_MAX_LENGTH', 25);
\define('REGISTER_USERNAME_FORCE_ASCII', 2);
\define('REGISTER_MIN_USER_AGE', 0);
+\define('REGISTER_ANTISPAM_ACTION', 0);
\define('GITHUB_PUBLIC_KEY', '');
\define('GITHUB_PRIVATE_KEY', '');
\define('TWITTER_PUBLIC_KEY', '');
@@ -214,7 +215,6 @@
\define('BLACKLIST_SFS_USERNAME', '90percentile');
\define('BLACKLIST_SFS_EMAIL_ADDRESS', 'moreThanOnce');
\define('BLACKLIST_SFS_IP_ADDRESS', '90percentile');
-\define('BLACKLIST_SFS_ACTION', 'disable');
\define('ENABLE_ENTERPRISE_MODE', 0);
\define('MESSAGE_ENABLE_USER_CONSENT', 1);
\define('MODIFICATION_LOG_EXPIRATION', 0);
diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
index 8a7b127088a..722d25b6bf5 100644
--- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
+++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
@@ -33,6 +33,19 @@ static function (\wcf\event\session\PreserveVariablesCollecting $event) {
\wcf\system\event\listener\UsernameValidatingCheckCharactersListener::class
);
+ $eventHandler->register(
+ \wcf\event\user\RegistrationSpamChecking::class,
+ \wcf\system\event\listener\RegistrationSpamCheckingSfsListener::class
+ );
+ $eventHandler->register(
+ \wcf\event\page\ContactFormSpamChecking::class,
+ \wcf\system\event\listener\ContactFormSpamCheckingSfsListener::class
+ );
+ $eventHandler->register(
+ \wcf\event\message\MessageSpamChecking::class,
+ \wcf\system\event\listener\MessageSpamCheckingSfsListener::class
+ );
+
$eventHandler->register(
\wcf\event\package\PackageListChanged::class,
static function () {
diff --git a/wcfsetup/install/files/lib/data/comment/CommentAction.class.php b/wcfsetup/install/files/lib/data/comment/CommentAction.class.php
index 36c4e0197a7..a8fa98041ad 100644
--- a/wcfsetup/install/files/lib/data/comment/CommentAction.class.php
+++ b/wcfsetup/install/files/lib/data/comment/CommentAction.class.php
@@ -12,10 +12,12 @@
use wcf\data\object\type\ObjectType;
use wcf\data\object\type\ObjectTypeCache;
use wcf\data\user\User;
+use wcf\event\message\MessageSpamChecking;
use wcf\system\bbcode\BBCodeHandler;
use wcf\system\captcha\CaptchaHandler;
use wcf\system\comment\CommentHandler;
use wcf\system\comment\manager\ICommentManager;
+use wcf\system\event\EventHandler;
use wcf\system\exception\NamedUserException;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\SystemException;
@@ -36,6 +38,7 @@
use wcf\system\WCF;
use wcf\util\MessageUtil;
use wcf\util\UserRegistrationUtil;
+use wcf\util\UserUtil;
/**
* Executes comment-related actions.
@@ -403,6 +406,16 @@ public function validateAddComment()
if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
throw new PermissionDeniedException();
}
+
+ $event = new MessageSpamChecking(
+ $this->parameters['htmlInputProcessor'],
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ );
+ EventHandler::getInstance()->fire($event);
+ if ($event->defaultPrevented()) {
+ throw new PermissionDeniedException();
+ }
}
/**
@@ -564,6 +577,16 @@ public function validateAddResponse()
$this->validateGetGuestDialog();
$this->validateMessage(true);
+
+ $event = new MessageSpamChecking(
+ $this->parameters['htmlInputProcessor'],
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ );
+ EventHandler::getInstance()->fire($event);
+ if ($event->defaultPrevented()) {
+ throw new PermissionDeniedException();
+ }
}
/**
diff --git a/wcfsetup/install/files/lib/event/message/MessageSpamChecking.class.php b/wcfsetup/install/files/lib/event/message/MessageSpamChecking.class.php
new file mode 100644
index 00000000000..82effb222fd
--- /dev/null
+++ b/wcfsetup/install/files/lib/event/message/MessageSpamChecking.class.php
@@ -0,0 +1,30 @@
+
+ * @since 6.1
+ */
+final class MessageSpamChecking implements IInterruptableEvent
+{
+ use TInterruptableEvent;
+
+ public function __construct(
+ public readonly HtmlInputProcessor $processor,
+ public readonly ?User $user = null,
+ public readonly string $ipAddress = '',
+ public readonly string $subject = '',
+ ) {
+ }
+}
diff --git a/wcfsetup/install/files/lib/event/page/ContactFormSpamChecking.class.php b/wcfsetup/install/files/lib/event/page/ContactFormSpamChecking.class.php
new file mode 100644
index 00000000000..b744442090b
--- /dev/null
+++ b/wcfsetup/install/files/lib/event/page/ContactFormSpamChecking.class.php
@@ -0,0 +1,27 @@
+
+ * @since 6.1
+ */
+final class ContactFormSpamChecking implements IInterruptableEvent
+{
+ use TInterruptableEvent;
+
+ public function __construct(
+ public readonly string $email,
+ public readonly string $ipAddress,
+ public readonly array $messages,
+ ) {
+ }
+}
diff --git a/wcfsetup/install/files/lib/event/user/RegistrationSpamChecking.class.php b/wcfsetup/install/files/lib/event/user/RegistrationSpamChecking.class.php
new file mode 100644
index 00000000000..8137512020e
--- /dev/null
+++ b/wcfsetup/install/files/lib/event/user/RegistrationSpamChecking.class.php
@@ -0,0 +1,41 @@
+
+ * @since 6.1
+ */
+final class RegistrationSpamChecking implements IPsr14Event
+{
+ private array $matches = [];
+
+ public function __construct(
+ public readonly string $username,
+ public readonly string $email,
+ public readonly string $ipAddress
+ ) {
+ }
+
+ public function hasMatches(): bool
+ {
+ return $this->matches !== [];
+ }
+
+ public function addMatch(string $key): void
+ {
+ $this->matches[$key] = $key;
+ }
+
+ public function getMatches(): array
+ {
+ return \array_values($this->matches);
+ }
+}
diff --git a/wcfsetup/install/files/lib/form/ContactForm.class.php b/wcfsetup/install/files/lib/form/ContactForm.class.php
index 5e04a5e4dbe..3e3547bc51c 100644
--- a/wcfsetup/install/files/lib/form/ContactForm.class.php
+++ b/wcfsetup/install/files/lib/form/ContactForm.class.php
@@ -2,11 +2,13 @@
namespace wcf\form;
-use wcf\data\blacklist\entry\BlacklistEntry;
+use wcf\data\contact\option\ContactOption;
use wcf\data\contact\option\ContactOptionAction;
use wcf\data\contact\recipient\ContactRecipientList;
+use wcf\event\page\ContactFormSpamChecking;
use wcf\system\attachment\AttachmentHandler;
use wcf\system\email\Mailbox;
+use wcf\system\event\EventHandler;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\UserInputException;
@@ -169,16 +171,34 @@ public function validate()
}
}
- if (BLACKLIST_SFS_ENABLE) {
- $matches = BlacklistEntry::getMatches(
- '',
- $this->email,
- UserUtil::getIpAddress()
- );
- if ($matches !== [] && BLACKLIST_SFS_ACTION === 'block') {
- throw new PermissionDeniedException();
+ $this->handleSpamCheck();
+ }
+
+ private function handleSpamCheck(): void
+ {
+ $messages = [];
+ foreach ($this->optionHandler->getOptions() as $option) {
+ $object = $option['object'];
+ \assert($object instanceof ContactOption);
+ if (!$object->isMessage || !$object->getOptionValue()) {
+ continue;
+ }
+
+ $messages[] = $object->getOptionValue();
+ if ($object->optionType === 'date' && !$object->getOptionValue()) {
+ continue;
}
}
+
+ $spamCheckEvent = new ContactFormSpamChecking(
+ $this->email,
+ UserUtil::getIpAddress(),
+ $messages,
+ );
+ EventHandler::getInstance()->fire($spamCheckEvent);
+ if ($spamCheckEvent->defaultPrevented()) {
+ throw new PermissionDeniedException();
+ }
}
/**
diff --git a/wcfsetup/install/files/lib/form/MessageForm.class.php b/wcfsetup/install/files/lib/form/MessageForm.class.php
index 6ef0400cf58..332da7da8f0 100644
--- a/wcfsetup/install/files/lib/form/MessageForm.class.php
+++ b/wcfsetup/install/files/lib/form/MessageForm.class.php
@@ -6,8 +6,10 @@
use wcf\data\smiley\category\SmileyCategory;
use wcf\data\smiley\Smiley;
use wcf\data\smiley\SmileyCache;
+use wcf\event\message\MessageSpamChecking;
use wcf\system\attachment\AttachmentHandler;
use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\event\EventHandler;
use wcf\system\exception\UserInputException;
use wcf\system\html\input\HtmlInputProcessor;
use wcf\system\html\upcast\HtmlUpcastProcessor;
@@ -16,6 +18,7 @@
use wcf\system\WCF;
use wcf\util\MessageUtil;
use wcf\util\StringUtil;
+use wcf\util\UserUtil;
/**
* MessageForm is an abstract form implementation for a message with optional captcha support.
@@ -347,4 +350,22 @@ public function assignVariables()
'tmpHash' => $this->tmpHash,
]);
}
+
+ /**
+ * This method triggers the event for the spam check and returns the result.
+ *
+ * @since 6.1
+ */
+ protected function messageIsProbablySpam(): bool
+ {
+ $event = new MessageSpamChecking(
+ $this->htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ $this->subject,
+ );
+ EventHandler::getInstance()->fire($event);
+
+ return $event->defaultPrevented();
+ }
}
diff --git a/wcfsetup/install/files/lib/form/RegisterForm.class.php b/wcfsetup/install/files/lib/form/RegisterForm.class.php
index ccce21d216a..8b619abfb46 100644
--- a/wcfsetup/install/files/lib/form/RegisterForm.class.php
+++ b/wcfsetup/install/files/lib/form/RegisterForm.class.php
@@ -6,11 +6,11 @@
use wcf\acp\form\UserAddForm;
use wcf\action\EmailValidationAction;
use wcf\action\UsernameValidationAction;
-use wcf\data\blacklist\entry\BlacklistEntry;
use wcf\data\object\type\ObjectType;
use wcf\data\user\group\UserGroup;
use wcf\data\user\User;
use wcf\data\user\UserAction;
+use wcf\event\user\RegistrationSpamChecking;
use wcf\system\captcha\CaptchaHandler;
use wcf\system\email\Email;
use wcf\system\email\mime\MimePartFacade;
@@ -89,6 +89,7 @@ class RegisterForm extends UserAddForm
* list of fields that have matches in the blacklist
* @var string[]
* @since 5.2
+ * @deprecated 6.1
*/
public $blacklistMatches = [];
@@ -108,6 +109,11 @@ class RegisterForm extends UserAddForm
*/
public bool $termsConfirmed = false;
+ /**
+ * @since 6.1
+ */
+ private RegistrationSpamChecking $spamCheckEvent;
+
/**
* @inheritDoc
*/
@@ -210,17 +216,12 @@ public function validate()
throw new UserInputException('registrationStartTime', []);
}
- if (BLACKLIST_SFS_ENABLE) {
- $this->blacklistMatches = BlacklistEntry::getMatches(
- $this->username,
- $this->email,
- UserUtil::getIpAddress()
+ $this->spamCheckEvent = new RegistrationSpamChecking($this->username, $this->email, UserUtil::getIpAddress());
+ EventHandler::getInstance()->fire($this->spamCheckEvent);
+ if ($this->spamCheckEvent->hasMatches() && \REGISTER_ANTISPAM_ACTION === 'block') {
+ throw new NamedUserException(
+ WCF::getLanguage()->getDynamicVariable('wcf.user.register.error.blacklistMatches')
);
- if (!empty($this->blacklistMatches) && BLACKLIST_SFS_ACTION === 'block') {
- throw new NamedUserException(
- WCF::getLanguage()->getDynamicVariable('wcf.user.register.error.blacklistMatches')
- );
- }
}
if (REGISTER_ENABLE_DISCLAIMER && !$this->termsConfirmed) {
@@ -407,7 +408,7 @@ public function save()
// generate activation code
$addDefaultGroups = true;
if (
- !empty($this->blacklistMatches)
+ $this->spamCheckEvent->hasMatches()
|| (REGISTER_ACTIVATION_METHOD & User::REGISTER_ACTIVATION_USER && !$registerVia3rdParty)
|| (REGISTER_ACTIVATION_METHOD & User::REGISTER_ACTIVATION_ADMIN)
) {
@@ -425,7 +426,7 @@ public function save()
'username' => $this->username,
'email' => $this->email,
'password' => $this->password,
- 'blacklistMatches' => (!empty($this->blacklistMatches)) ? JSON::encode($this->blacklistMatches) : '',
+ 'blacklistMatches' => $this->spamCheckEvent->hasMatches() ? JSON::encode($this->spamCheckEvent->getMatches()) : '',
'signatureEnableHtml' => 1,
]),
'groups' => $this->groupIDs,
@@ -442,11 +443,11 @@ public function save()
WCF::getSession()->changeUser($user);
// activation management
- if (REGISTER_ACTIVATION_METHOD == User::REGISTER_ACTIVATION_NONE && empty($this->blacklistMatches)) {
+ if (REGISTER_ACTIVATION_METHOD == User::REGISTER_ACTIVATION_NONE && !$this->spamCheckEvent->hasMatches()) {
$this->message = 'wcf.user.register.success';
UserGroupAssignmentHandler::getInstance()->checkUsers([$user->userID]);
- } elseif (REGISTER_ACTIVATION_METHOD & User::REGISTER_ACTIVATION_USER && empty($this->blacklistMatches)) {
+ } elseif (REGISTER_ACTIVATION_METHOD & User::REGISTER_ACTIVATION_USER && !$this->spamCheckEvent->hasMatches()) {
// registering via 3rdParty leads to instant activation
if ($registerVia3rdParty) {
$this->message = 'wcf.user.register.success';
@@ -463,7 +464,7 @@ public function save()
$email->send();
$this->message = 'wcf.user.register.success.needActivation';
}
- } elseif (REGISTER_ACTIVATION_METHOD & User::REGISTER_ACTIVATION_ADMIN || !empty($this->blacklistMatches)) {
+ } elseif (REGISTER_ACTIVATION_METHOD & User::REGISTER_ACTIVATION_ADMIN || $this->spamCheckEvent->hasMatches()) {
$this->message = 'wcf.user.register.success.awaitActivation';
}
diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php
index f7fe627d744..aa52e18efdb 100644
--- a/wcfsetup/install/files/lib/system/WCF.class.php
+++ b/wcfsetup/install/files/lib/system/WCF.class.php
@@ -499,6 +499,9 @@ protected function defineLegacyOptions(): void
// The option to count guests in the online record was removed with version 6.1.
// https://github.com/WoltLab/WCF/issues/5888
\define('USERS_ONLINE_RECORD_NO_GUESTS', 1);
+
+ // The option for the SFS action has been converted into a general option with version 6.1.
+ \define('BLACKLIST_SFS_ACTION', 'disable');
}
/**
diff --git a/wcfsetup/install/files/lib/system/event/listener/ContactFormSpamCheckingSfsListener.class.php b/wcfsetup/install/files/lib/system/event/listener/ContactFormSpamCheckingSfsListener.class.php
new file mode 100644
index 00000000000..a26c4ce9f8f
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/event/listener/ContactFormSpamCheckingSfsListener.class.php
@@ -0,0 +1,28 @@
+
+ * @since 6.1
+ */
+final class ContactFormSpamCheckingSfsListener
+{
+ public function __invoke(ContactFormSpamChecking $event): void
+ {
+ if (!\BLACKLIST_SFS_ENABLE) {
+ return;
+ }
+
+ if (BlacklistEntry::getMatches('', $event->email, $event->ipAddress) !== []) {
+ $event->preventDefault();
+ }
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/event/listener/MessageSpamCheckingSfsListener.class.php b/wcfsetup/install/files/lib/system/event/listener/MessageSpamCheckingSfsListener.class.php
new file mode 100644
index 00000000000..3b51d828e6f
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/event/listener/MessageSpamCheckingSfsListener.class.php
@@ -0,0 +1,44 @@
+
+ * @since 6.1
+ */
+final class MessageSpamCheckingSfsListener
+{
+ public function __invoke(MessageSpamChecking $event): void
+ {
+ if (!\BLACKLIST_SFS_ENABLE) {
+ return;
+ }
+
+ if ($event->user !== null) {
+ // Skip spam check for admins and moderators
+ $userProfile = UserProfileRuntimeCache::getInstance()->getObject($event->user->userID);
+ if (
+ $userProfile->getPermission('admin.general.canUseAcp')
+ || $userProfile->getPermission('mod.general.canUseModeration')
+ ) {
+ return;
+ }
+ }
+
+ if (BlacklistEntry::getMatches(
+ $event->user ? $event->user->username : '',
+ $event->user ? $event->user->email : '',
+ $event->ipAddress,
+ ) !== []) {
+ $event->preventDefault();
+ }
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/event/listener/RegistrationSpamCheckingSfsListener.class.php b/wcfsetup/install/files/lib/system/event/listener/RegistrationSpamCheckingSfsListener.class.php
new file mode 100644
index 00000000000..4f58c320ca9
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/event/listener/RegistrationSpamCheckingSfsListener.class.php
@@ -0,0 +1,28 @@
+
+ * @since 6.1
+ */
+final class RegistrationSpamCheckingSfsListener
+{
+ public function __invoke(RegistrationSpamChecking $event): void
+ {
+ if (!\BLACKLIST_SFS_ENABLE) {
+ return;
+ }
+
+ foreach (BlacklistEntry::getMatches($event->username, $event->email, $event->ipAddress) as $match) {
+ $event->addMatch($match);
+ }
+ }
+}
diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml
index 65827527d91..fbf5278562f 100644
--- a/wcfsetup/install/lang/de.xml
+++ b/wcfsetup/install/lang/de.xml
@@ -1324,7 +1324,6 @@ Die Entwickler-Lizenz gestattet ausschließlich den Einsatz während der Entwick
-
@@ -1717,7 +1716,6 @@ Als Benachrichtigungs-URL in der Konfiguration der sofortigen Zahlungsbestätigu
-
-
Hinweis: Es werden zu keinem Zeitpunkt Daten an Drittseiten übertragen.
@@ -1739,10 +1737,10 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
- Mehrfach gemeldet.]]>
- Top 10%.]]>
-
-
- - Deaktivierung, erfordert manuelle Freischaltung ausdrücklich empfohlen.]]>
-
+
+
+ - Registrierung erfordert manuelle Freischaltung ausdrücklich empfohlen.]]>
+
diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml
index d333dd79f69..42d17245628 100644
--- a/wcfsetup/install/lang/en.xml
+++ b/wcfsetup/install/lang/en.xml
@@ -1302,7 +1302,6 @@ The developer license permits exclusively the use during the development as well
-
@@ -1700,7 +1699,6 @@ When prompted for the notification URL for the instant payment notifications, pl
-
-
Notice: No data is transferred to a third party service at any point in the process.
@@ -1722,10 +1720,10 @@ The database is carefully maintained, but there will be always be a margin of er
- Multiple reports.]]>
- Top 10%.]]>
-
-
- - Disable and require manual approval.]]>
-
+
+
+ - Disable and require manual approval.]]>
+