From a6a173314bd525c5dc9d4b5f74e47b4bcf5e7725 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Roland=20H=C3=A4der?= Date: Sat, 24 Jun 2023 12:21:22 +0200 Subject: [PATCH] Continued: - rewrote api.py's router functions in a more flexible way: mode,value,amount - for now only a few modes are supported: domain, reverse, reason, block_level --- api.py | 129 +++++++++++++++++++------------- fba/models/blocks.py | 17 +++++ templates/rss.xml | 2 +- templates/views/index.html | 9 ++- templates/views/scoreboard.html | 4 +- templates/views/top.html | 22 +++--- templates/widgets/links.tpl | 4 +- 7 files changed, 118 insertions(+), 69 deletions(-) diff --git a/api.py b/api.py index c3c7990..3e0e825 100644 --- a/api.py +++ b/api.py @@ -36,6 +36,8 @@ from fba.helpers import tidyup from fba.http import network +from fba.models import blocks + router = fastapi.FastAPI(docs_url=config.get("base_url") + "/docs", redoc_url=config.get("base_url") + "/redoc") templates = Jinja2Templates(directory="templates") @@ -93,33 +95,67 @@ def api_scoreboard(mode: str, amount: int): return scores @router.get(config.get("base_url") + "/api/index.json", response_class=JSONResponse) -def api_blocked(domain: str = None, reason: str = None, reverse: str = None): - if domain is None and reason is None and reverse is None: - raise HTTPException(status_code=400, detail="No filter specified") - - if reason is not None: - reason = re.sub("(%|_)", "", tidyup.reason(reason)) - if len(reason) < 3: - raise HTTPException(status_code=400, detail="Keyword is shorter than three characters") - - if domain is not None: - domain = tidyup.domain(domain) - if not validators.domain(domain.split("/")[0]): - raise HTTPException(status_code=500, detail="Invalid domain") +def api_index(request: Request, mode: str, value: str, amount: int): + if mode is None or value is None or amount is None: + raise HTTPException(status_code=500, detail="No filter specified") + elif amount > 500: + raise HTTPException(status_code=500, detail=f"amount={amount} is to big") + + domain = whildchar = punycode = reason = None + + if mode == "block_level": + database.cursor.execute( + "SELECT blocker, blocked, block_level, reason, first_seen, last_seen FROM blocks WHERE block_level = ? LIMIT ?", [value, amount] + ) + elif mode in ["domain", "reverse"]: + domain = tidyup.domain(value) + if not utils.is_domain_wanted(domain): + raise HTTPException(status_code=500, detail=f"domain='{domain}' is not wanted") wildchar = "*." + ".".join(domain.split(".")[-domain.count("."):]) punycode = domain.encode('idna').decode('utf-8') + elif mode == "reason": + reason = re.sub("(%|_)", "", tidyup.reason(value)) + if len(reason) < 3: + raise HTTPException(status_code=400, detail="Keyword is shorter than three characters") - database.cursor.execute("SELECT blocker, blocked, block_level, reason, first_seen, last_seen FROM blocks WHERE blocked = ? OR blocked = ? OR blocked = ? OR blocked = ? OR blocked = ? OR blocked = ? ORDER BY first_seen ASC", - (domain, "*." + domain, wildchar, utils.get_hash(domain), punycode, "*." + punycode)) - elif reverse is not None: - reverse = tidyup.domain(reverse) - if not validators.domain(reverse): - raise HTTPException(status_code=500, detail="Invalid domain") - - database.cursor.execute("SELECT blocker, blocked, block_level, reason, first_seen, last_seen FROM blocks WHERE blocker = ? ORDER BY first_seen ASC", [reverse]) - else: - database.cursor.execute("SELECT blocker, blocked, block_level, reason, first_seen, last_seen FROM blocks WHERE reason like ? AND reason != '' ORDER BY first_seen ASC", ["%" + reason + "%"]) + if mode == "domain": + database.cursor.execute("SELECT blocker, blocked, block_level, reason, first_seen, last_seen \ +FROM blocks \ +WHERE blocked = ? OR blocked = ? OR blocked = ? OR blocked = ? OR blocked = ? OR blocked = ? ORDER BY first_seen ASC LIMIT ?", + [ + domain, + "*." + domain, + wildchar, + utils.get_hash(domain), + punycode, + "*." + punycode, + amount + ] + ) + elif mode == "reverse": + database.cursor.execute("SELECT blocker, blocked, block_level, reason, first_seen, last_seen \ +FROM blocks \ +WHERE blocker = ? OR blocker = ? OR blocker = ? OR blocker = ? OR blocker = ? OR blocker = ? \ +ORDER BY first_seen ASC \ +LIMIT ?", [ + domain, + "*." + domain, + wildchar, + utils.get_hash(domain), + punycode, + "*." + punycode, + amount + ]) + elif mode == "reason": + database.cursor.execute("SELECT blocker, blocked, block_level, reason, first_seen, last_seen \ +FROM blocks \ +WHERE reason LIKE ? AND reason != '' \ +ORDER BY first_seen ASC \ +LIMIT ?", [ + "%" + reason + "%", + amount + ]) blocklist = database.cursor.fetchall() @@ -225,51 +261,42 @@ def index(request: Request): }) @router.get(config.get("base_url") + "/top") -def top(request: Request, domain: str = None, reason: str = None, reverse: str = None): - if domain == "" or reason == "" or reverse == "": - raise HTTPException(status_code=500, detail="Insufficient parameter provided") - +def top(request: Request, mode: str, value: str, amount: int = 500): response = requests.get(f"http://{config.get('host')}:{config.get('port')}{config.get('base_url')}/api/info.json") if not response.ok: raise HTTPException(status_code=response.status_code, detail=response.text) + elif mode == "" or value == "" or amount == 0: + raise HTTPException(status_code=500, detail="Parameter mode, value and amount must always be set") + elif amount > 500: + raise HTTPException(status_code=500, detail=f"amount='{amount}' is to big") info = response.json() response = None + blocklist = list() - if domain is not None: - domain = tidyup.domain(domain) - if not validators.domain(domain.split("/")[0]): - raise HTTPException(status_code=500, detail="Invalid domain") + if mode == "block_level" and not blocks.is_valid_level(value): + raise HTTPException(status_code=500, detail="Invalid block level provided") + elif mode in ["domain", "reverse"] and not utils.is_domain_wanted(value): + raise HTTPException(status_code=500, detail="Invalid or blocked domain specified") - response = requests.get(f"http://{config.get('host')}:{config.get('port')}{config.get('base_url')}/api/index.json?domain={domain}") - elif reason is not None: - response = requests.get(f"http://{config.get('host')}:{config.get('port')}{config.get('base_url')}/api/index.json?reason={reason}") - elif reverse is not None: - reverse = tidyup.domain(reverse) - if not validators.domain(reverse): - raise HTTPException(status_code=500, detail="Invalid domain") - - response = requests.get(f"http://{config.get('host')}:{config.get('port')}{config.get('base_url')}/api/index.json?reverse={reverse}") + response = requests.get(f"http://{config.get('host')}:{config.get('port')}{config.get('base_url')}/api/index.json?mode={mode}&value={value}&amount={amount}") if response is not None: - if not response.ok: - raise HTTPException(status_code=response.status_code, detail=response.text) - blocklist = response.json() - for block_level in blocklist: - for block in blocklist[block_level]: - block["first_seen"] = datetime.utcfromtimestamp(block["first_seen"]).strftime(config.get("timestamp_format")) - block["last_seen"] = datetime.utcfromtimestamp(block["last_seen"]).strftime(config.get("timestamp_format")) + for block_level in blocklist: + for block in blocklist[block_level]: + block["first_seen"] = datetime.utcfromtimestamp(block["first_seen"]).strftime(config.get("timestamp_format")) + block["last_seen"] = datetime.utcfromtimestamp(block["last_seen"]).strftime(config.get("timestamp_format")) return templates.TemplateResponse("views/top.html", { "request": request, - "domain" : domain, + "mode" : mode if response is not None else None, + "value" : value if response is not None else None, + "amount" : amount if response is not None else None, "blocks" : blocklist, - "reason" : reason, - "reverse": reverse, - "info" : info + "info" : info, }) @router.get(config.get("base_url") + "/rss") diff --git a/fba/models/blocks.py b/fba/models/blocks.py index d568d5b..2f66445 100644 --- a/fba/models/blocks.py +++ b/fba/models/blocks.py @@ -164,3 +164,20 @@ def add_instance(blocker: str, blocked: str, reason: str, block_level: str): ]) logger.debug("EXIT!") + +def is_valid_level(block_level: str) -> bool: + logger.debug("block_level='%s' - CALLED!", block_level) + if not isinstance(block_level, str): + raise ValueError(f"Parameter block_level[]='{type(block_level)}' is not of type 'str'") + elif block_level == "": + raise ValueError("Parameter 'block_level' is empty") + + # Query database + database.cursor.execute( + "SELECT block_level FROM blocks WHERE block_level = ? LIMIT 1", [block_level] + ) + + valid = database.cursor.fetchone() is not None + + logger.debug("valid='%s' - EXIT!", valid) + return valid diff --git a/templates/rss.xml b/templates/rss.xml index f254a8c..5d2c06d 100644 --- a/templates/rss.xml +++ b/templates/rss.xml @@ -9,7 +9,7 @@ {{block['blocker']}} has applied '{{block['block_level']}}' restriction to {{block['blocked']}} {{block['reason']}} - https://{{hostname}}/top?reverse={{block['blocker']}} + https://{{hostname}}/top?mode=reverse&domain={{block['blocker']}} {{block['first_seen']}} {% endfor %} diff --git a/templates/views/index.html b/templates/views/index.html index 79a0cd4..5d0609f 100644 --- a/templates/views/index.html +++ b/templates/views/index.html @@ -7,19 +7,22 @@ {% block content %}

Enter a Domain

- + +

Enter a Reason

- + +

Reverse search

- + +
diff --git a/templates/views/scoreboard.html b/templates/views/scoreboard.html index b08110c..b8449ad 100644 --- a/templates/views/scoreboard.html +++ b/templates/views/scoreboard.html @@ -42,10 +42,12 @@ {{loop.index}} - {% if mode in ('software', 'command', 'error_code', 'detection_mode', 'avg_peers', 'obfuscator', 'obfuscation', 'block_level') %} + {% if mode in ('software', 'command', 'error_code', 'detection_mode', 'avg_peers', 'obfuscator', 'obfuscation') %} {{entry['domain']}} {% elif entry['domain'] == None %} - + {% elif mode == 'block_level' %} + {{entry['domain']}} {% else %} {% with domain=entry['domain'] %} {% include "widgets/links.tpl" %} diff --git a/templates/views/top.html b/templates/views/top.html index 0489ba4..c18da52 100644 --- a/templates/views/top.html +++ b/templates/views/top.html @@ -1,23 +1,23 @@ {% extends "base.html" %} -{% block title %}{% if domain %} - Instances that block {{domain}}{% elif reverse %} - Instances that are blocked by {{reverse}}{% endif %}{% endblock %} +{% block title %}{% if mode == 'domain' %} - Instances that block {{value}}{% elif mode == 'reverse' %} - Instances that are blocked by {{value}}{% elif mode == 'reason' %} - Instances having block reason {{value}}{% endif %}{% endblock %} {% block rss %} {{ super() }} - {% if domain %} - - {% elif reverse %} - + {% if mode == 'domain' %} + + {% elif mode == 'reverse' %} + {% endif %} {% endblock %} {% block header %} - {% if reason %} -

Instances that use "{{reason}}" in their reason

- {% elif reverse %} -

Instances that are blocked by {{reverse}}

- {% elif domain %} -

Instances that block {{domain}}

+ {% if mode == 'reason' %} +

Instances that use "{{value}}" in their reason

+ {% elif mode == 'reverse' %} +

Instances that are blocked by {{value}}

+ {% elif mode == 'domain' %} +

Instances that block {{value}}

{% endif %} {% endblock %} diff --git a/templates/widgets/links.tpl b/templates/widgets/links.tpl index e946612..46b435a 100644 --- a/templates/widgets/links.tpl +++ b/templates/widgets/links.tpl @@ -1,3 +1,3 @@ -[D] -[R] +[D] +[R] {{domain}} -- 2.39.5