]> git.ipfire.org Git - ipfire.org.git/commitdiff
people: Check StopForumSpam when registering accounts
authorMichael Tremer <michael.tremer@ipfire.org>
Sun, 29 Sep 2019 17:06:59 +0000 (18:06 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Sun, 29 Sep 2019 17:06:59 +0000 (18:06 +0100)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/accounts.py
src/backend/base.py
src/backend/zeiterfassung.py
src/templates/auth/register-spam.html [new file with mode: 0644]
src/web/auth.py

index 24cf8d7c32e79332b58bdef3467d1883056ba771..eef9578e6fe0e4a36f2dc1a849af8239fb4dc751 100644 (file)
@@ -114,6 +114,7 @@ templates_auth_DATA = \
        src/templates/auth/activated.html \
        src/templates/auth/login.html \
        src/templates/auth/register.html \
+       src/templates/auth/register-spam.html \
        src/templates/auth/register-success.html
 
 templates_authdir = $(templatesdir)/auth
index 71253a44b6fda195ec53b68d69e98ecdf90ede04..1c348a2a639374b0da27c22e3e835eb6db8dcaae 100644 (file)
@@ -2,6 +2,7 @@
 # encoding: utf-8
 
 import datetime
+import json
 import ldap
 import ldap.modlist
 import logging
@@ -9,6 +10,7 @@ import os
 import phonenumbers
 import sshpubkeys
 import time
+import tornado.httpclient
 import urllib.parse
 import urllib.request
 import zxcvbn
@@ -163,6 +165,15 @@ class Accounts(Object):
                        "(&(objectClass=inetOrgPerson)(|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
                        % (number, number, number, number))
 
+       @tornado.gen.coroutine
+       def check_spam(self, uid, email, address):
+               sfs = StopForumSpam(self.backend, uid, email, address)
+
+               # Get spam score
+               score = yield sfs.check()
+
+               return score >= 50
+
        # Registration
 
        def register(self, uid, email, first_name, last_name):
@@ -920,6 +931,85 @@ class Account(Object):
                        ])
 
 
+class StopForumSpam(Object):
+       def init(self, uid, email, address):
+               self.uid, self.email, self.address = uid, email, address
+
+       @tornado.gen.coroutine
+       def send_request(self, **kwargs):
+               arguments = {
+                       "json" : "1",
+               }
+               arguments.update(kwargs)
+
+               # Create request
+               request = tornado.httpclient.HTTPRequest(
+                       "https://api.stopforumspam.org/api", method="POST")
+               request.body = urllib.parse.urlencode(arguments)
+
+               # Send the request
+               response = yield self.backend.http_client.fetch(request)
+
+               # Decode the JSON response
+               return json.loads(response.body.decode())
+
+       @tornado.gen.coroutine
+       def check_address(self):
+               response = yield self.send_request(ip=self.address)
+
+               try:
+                       confidence = response["ip"]["confidence"]
+               except KeyError:
+                       confidence = 100
+
+               logging.debug("Confidence for %s: %s" % (self.address, confidence))
+
+               return confidence
+
+       @tornado.gen.coroutine
+       def check_username(self):
+               response = yield self.send_request(username=self.uid)
+
+               try:
+                       confidence = response["username"]["confidence"]
+               except KeyError:
+                       confidence = 100
+
+               logging.debug("Confidence for %s: %s" % (self.uid, confidence))
+
+               return confidence
+
+       @tornado.gen.coroutine
+       def check_email(self):
+               response = yield self.send_request(email=self.email)
+
+               try:
+                       confidence = response["email"]["confidence"]
+               except KeyError:
+                       confidence = 100
+
+               logging.debug("Confidence for %s: %s" % (self.email, confidence))
+
+               return confidence
+
+       @tornado.gen.coroutine
+       def check(self, threshold=95):
+               """
+                       This function tries to detect if we have a spammer.
+
+                       To honour the privacy of our users, we only send the IP
+                       address and username and if those are on the database, we
+                       will send the email address as well.
+               """
+               confidences = yield [self.check_address(), self.check_username()]
+
+               if any((c < threshold for c in confidences)):
+                       confidences += yield [self.check_email()]
+
+               # Build a score based on the lowest confidence
+               return 100 - min(confidences)
+
+
 if __name__ == "__main__":
        a = Accounts()
 
index 075546e014a2f88f65fdae37ef5f7092a0a7c284..170276652f8e2e1ebee33d810cd784fd2e3539cd 100644 (file)
@@ -3,6 +3,7 @@
 import configparser
 import io
 import tornado.gen
+import tornado.httpclient
 
 from . import accounts
 from . import blog
@@ -44,6 +45,12 @@ class Backend(object):
                # Setup database.
                self.setup_database()
 
+               # Create HTTPClient
+               self.http_client = tornado.httpclient.AsyncHTTPClient(
+                       defaults = {
+                               "User-Agent" : "IPFireWebApp",
+                       }
+               )
                # Initialize settings first.
                self.settings = settings.Settings(self)
                self.memcache = memcached.Memcached(self)
@@ -91,6 +98,7 @@ class Backend(object):
        def run_task(self, task, *args, **kwargs):
                tasks = {
                        "check-mirrors"     : self.mirrors.check_all,
+                       "check-spam-score"  : self.accounts.check_spam_score,
                        "cleanup"           : self.cleanup,
                        "scan-files"        : self.releases.scan_files,
                        "send-all-messages" : self.messages.queue.send_all,
index aa0173e2b740d7cf2d75e14d1e7337e463b572ec..158d611349fc816f6ee0331f71f7e1b2c54909c5 100644 (file)
@@ -23,13 +23,6 @@ class ZeiterfassungClient(Object):
                if not all((self.url, self.api_key, self.api_secret)):
                        raise RuntimeError("%s is not configured" % self.__class__.__name__)
 
-               # Create HTTPClient
-               self.http_client = tornado.httpclient.AsyncHTTPClient(
-                       defaults = {
-                               "User-Agent" : "IPFireWebApp",
-                       }
-               )
-
        def _make_signature(self, method, path, body):
                # Empty since we only support POST
                canonical_query = ""
@@ -63,7 +56,7 @@ class ZeiterfassungClient(Object):
                )
 
                # Send the request
-               response = yield self.http_client.fetch(request)
+               response = yield self.backend.http_client.fetch(request)
 
                # Decode the JSON response
                d = json.loads(response.body.decode())
diff --git a/src/templates/auth/register-spam.html b/src/templates/auth/register-spam.html
new file mode 100644 (file)
index 0000000..6b44690
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends "../base.html" %}
+
+{% block title %}{{ _("Oops!") }}{% end block %}
+
+{% block content %}
+       <div class="row justify-content-center my-5">
+               <div class="col-12 col-md-6">
+                       <div class="card bg-warning text-white p-md-5">
+                               <div class="card-body text-center">
+                                       <span class="fas fa-exclamation fa-5x my-4"></span>
+
+                                       <p class="lead">
+                                               {{ _("Unfortunately we could not create your account because you have shown up on our spam radar.") }}
+                                               {{ _("Please get in touch if you think that this is an error.") }}
+                                       </p>
+                               </div>
+                       </div>
+               </div>
+       </div>
+{% end block %}
index ce4a9c853d7682e46e53403ac6043191f231c36b..d2872695bbc2e74ab2e687bed41945bb2bc0feda 100644 (file)
@@ -90,7 +90,7 @@ class RegisterHandler(base.BaseHandler):
 
                self.render("auth/register.html")
 
-       @base.blacklisted
+       @tornado.gen.coroutine
        @base.ratelimit(minutes=24*60, requests=5)
        def post(self):
                uid   = self.get_argument("uid")
@@ -99,6 +99,14 @@ class RegisterHandler(base.BaseHandler):
                first_name = self.get_argument("first_name")
                last_name  = self.get_argument("last_name")
 
+               # Check if this is a spam account
+               is_spam = yield self.backend.accounts.check_spam(uid, email,
+                       address=self.get_remote_ip())
+
+               if is_spam:
+                       self.render("auth/register-spam.html")
+                       return
+
                # Register account
                try:
                        with self.db.transaction():