~/home of geeks

Denylist Service

· 592 Wörter · 3 Minute(n) Lesedauer

a security is showing his hand in sign of waving, in massive armor and helmet, looking grim, raising arm to signal stop, entrance of big corporation building in background, cyberpunk, sci-fi, dark mood, frontal view, long shot, full view

Beim Registrierungsprozess einer Seite kann man Denylisten einsetzen, um z. B. Usernamen, wie “Administrator” zu reservieren, oder zu Verhindern, dass jemand eine bekannte Wegwerf-Emailadresse verwendet. Grund genug, einen generischen Service hierfür zu schreiben.

Der von mir geschriebene Service liest eine Textdatei zeilenweise ein und fügt diese einem String-Set hinzu. Eine Datenbank wäre denkbar gewesen, ist aber in meinem Fall nicht notwendig. Die Listen sind vorerst statisch. Die Einträge der Listen werden getrimmt und in Kleinschreibung umgewandelt, da diese keine Rolle spielt. Zeilen, die mit einem “#” anfangen, werden als Kommentarzeilen ignoriert, genauso wie Zeilen mit nur Whitespace-Symbolen.

Das Beispiel für eine Liste von reservierten Benutzernamen sieht wie folgt aus:

adm
admin
administration
administrator
...
android
anon
anonymous
api
...
request
requests
reset
roc
root
rss
...
subscribe
subscriptions
suporte
support
...
usage
user
username
users
usuario
...
webhooks
webmail
webmaster
website
websites
welcome
widget
widgets
...

Auszug der benutzten Textdatei reserved-usernames.txt

Analog sieht die Textdatei für die nicht erlaubten Emaildomainnamen aus:

# https://gist.github.com/michenriksen/8710649
# 02.05.2018

0815.ru
0wnd.net
0wnd.org
10minutemail.co.za
10minutemail.com
123-m.com
1fsdfdsfsdf.tk
1pad.de
20minutemail.com
21cn.com
2fdgdfgdfgdf.tk
2prong.com
30minutemail.com
33mail.com
3trtretgfrfe.tk
4gfdsgfdgfd.tk
4warding.com
5ghgfhfghfgh.tk
6hjgjhgkilkj.tk
6paq.com
7tags.com
...

# https://gist.github.com/adamloving/4401361
0-mail.com
0815.ru
0clickemail.com
0wnd.net
0wnd.org
10minutemail.com
20minutemail.com
2prong.com
30minutemail.com
3d-painting.com
4warding.com
4warding.net
4warding.org
60minutemail.com
675hosting.com
675hosting.net
675hosting.org
...

# https://github.com/martenson/disposable-email-domains/blob/master/disposable_email_blacklist.conf
#

0-00.usa.cc
0-mail.com
001.igg.biz
027168.com
0815.ru
0815.ry
0815.su
0845.ru
0box.eu
0clickemail.com
0v.ro
0w.ro
0wnd.net
0wnd.org
0x207.info
1-8.biz
...

Auszug der benutzten Textdatei disposable-email-domains.txt

Da die Funktionalität beider Services ähnlich ist, habe ich eine Standardversion implementiert:

import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;

public class StringLookupService {
    private final Logger LOG = LoggerFactory.getLogger(getClass());

    private String resource;

    private boolean resultOnNullOrEmpty;

    private Set<String> lookupStrings = new HashSet<>();

    protected StringLookupService(String resource, boolean resultOnNullOrEmpty) {
        this.resource = resource;
        this.resultOnNullOrEmpty = resultOnNullOrEmpty;
    }

    @PostConstruct
    public void initialize() throws IOException {
        Resource resourceObject = new ClassPathResource(resource);
        LOG.info("Reading from {}", resourceObject.getFile());
        try (InputStream in = resourceObject.getInputStream()) {
            InputStreamReader rin = new InputStreamReader(in, "UTF-8");
            BufferedReader bin = new BufferedReader(rin);
            String line;
            while ((line = bin.readLine()) != null) {
                // Comments start with #
                if (!line.startsWith("#")) {
                    line = line.trim();
                    if (line.length() > 0) {
                        line = line.toLowerCase();
                        lookupStrings.add(line);
                    }
                }
            }
        }
    }

    protected final boolean hasString(String s) {
        if (Strings.isNullOrEmpty(s)) {
            return resultOnNullOrEmpty;
        }
        return lookupStrings.contains(s.trim().toLowerCase());
    }
}

Eine kleine Besonderheit ist der Boolsche resultOnNullOrEmpty Wert. Er kommt zum Tragen, wenn der gesuchte Text (die Eingabe) null oder leer ist. Wenn man eine Prüfung macht, ob etwas auf einer Liste ist, kann es sein, dass ein leerer Wert dazu führen soll, dass die Prüfung positiv verläuft, oder nicht. In meinem Fall sollen leere Werte als “nicht zulässig” und damit auf der Liste vorhanden gewertet werden.

Implementierung des Services zur Prüfung, ob ein Benutzernamen reserviert ist (und daher nicht benutzt werden darf). Diese Klasse dient dazu, verwendungsspezifische Methoden zur Verfüfung zu stellen.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class ReservedUsernameService extends StringLookupService {

    public ReservedUsernameService(@Value("${reserved.username.service.file}") String sourceFile) {
        super(sourceFile, true);
    }

    public boolean isReservedUsername(String username) {
        return super.hasString(username);
    }
}

Minimal komplexer ist der Service für die Emaildomains. Hier muss von der Eingabe erst noch der Domainanteil ermittelt werden.

import com.google.common.base.Strings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class DisposableEmailDomainsService extends StringLookupService {

    public DisposableEmailDomainsService(@Value("${disposable.email.domains.service.file}") String sourceFile) {
        super(sourceFile, true);
    }

    public boolean isDisposableEmail(String domain) {
        if (Strings.isNullOrEmpty(domain)) {
            return true;
        }
        domain = getDomain(domain);

        return super.hasString(domain);
    }

    public static final String getDomain(String fullEmailOrDomain) {
        final int indexOfAt = fullEmailOrDomain.indexOf('@');
        if (indexOfAt > -1) {
            fullEmailOrDomain = fullEmailOrDomain.substring(indexOfAt + 1, fullEmailOrDomain.length());
        }
        return fullEmailOrDomain;
    }
}