neptune/core/lib/opensearch.py

243 lines
8.1 KiB
Python
Raw Normal View History

2022-07-21 12:47:02 +00:00
from django.conf import settings
2022-08-02 16:10:41 +00:00
from django.contrib.auth.models import AnonymousUser
2022-07-21 12:47:02 +00:00
from opensearchpy import OpenSearch
2022-07-21 12:51:27 +00:00
from opensearchpy.exceptions import RequestError
2022-07-21 12:47:02 +00:00
2022-07-21 12:51:55 +00:00
from core.lib.threshold import annotate_num_chans, annotate_num_users, annotate_online
2022-07-21 12:47:02 +00:00
def initialise_opensearch():
2022-07-21 12:51:55 +00:00
"""
Inititialise the OpenSearch API endpoint.
"""
2022-07-21 12:47:02 +00:00
auth = (settings.OPENSEARCH_USERNAME, settings.OPENSEARCH_PASSWORD)
client = OpenSearch(
2022-07-21 12:47:10 +00:00
# fmt: off
2022-07-21 12:47:02 +00:00
hosts=[{"host": settings.OPENSEARCH_URL,
2022-07-21 12:47:10 +00:00
"port": settings.OPENSEARCH_PORT}],
2022-07-21 12:47:02 +00:00
http_compress=False, # enables gzip compression for request bodies
http_auth=auth,
# client_cert = client_cert_path,
# client_key = client_key_path,
use_ssl=settings.OPENSEARCH_TLS,
verify_certs=False,
ssl_assert_hostname=False,
ssl_show_warn=False,
# a_certs=ca_certs_path,
)
return client
2022-07-21 12:51:55 +00:00
client = initialise_opensearch()
def annotate_results(results_parsed):
"""
Accept a list of dict objects, search for the number of channels and users.
Add them to the object.
Mutate it in place. Does not return anything.
"""
# Figure out items with net (not discord)
nets = set()
for x in results_parsed:
if "net" in x:
nets.add(x["net"])
for net in nets:
# Annotate the online attribute from Threshold
nicks = [
x["nick"] for x in results_parsed if x["src"] == "irc" and x["net"] == net
]
channels = [
x["channel"]
for x in results_parsed
if x["src"] == "irc" and x["net"] == net
]
online_info = annotate_online(net, nicks)
2022-07-21 12:51:55 +00:00
# Annotate the number of users in the channel
num_users = annotate_num_users(net, channels)
2022-07-21 12:51:55 +00:00
# Annotate the number channels the user is on
num_chans = annotate_num_chans(net, nicks)
2022-07-21 12:51:55 +00:00
for item in results_parsed:
2022-07-29 21:41:53 +00:00
if "net" in item:
if item["net"] == net:
if "nick" in item:
if item["nick"] in online_info:
item["online"] = online_info[item["nick"]]
if "channel" in item:
if item["channel"] in num_users:
item["num_users"] = num_users[item["channel"]]
if "nick" in item:
if item["nick"] in num_chans:
item["num_chans"] = num_chans[item["nick"]]
2022-07-21 12:51:55 +00:00
def filter_blacklisted(user, response):
"""
Low level filter to take the raw OpenSearch response and remove
objects from it we want to keep secret.
Does not return, the object is mutated in place.
"""
response["redacted"] = 0
response["exemption"] = None
is_anonymous = isinstance(user, AnonymousUser)
2022-07-21 12:51:55 +00:00
# For every hit from ES
for item in list(response["hits"]["hits"]):
# For every blacklisted type
for blacklisted_type in settings.OPENSEARCH_BLACKLISTED.keys():
# Check this field we are matching exists
if blacklisted_type in item["_source"].keys():
content = item["_source"][blacklisted_type]
# For every item in the blacklisted array for the type
for blacklisted_item in settings.OPENSEARCH_BLACKLISTED[
blacklisted_type
]:
if blacklisted_item in str(content):
# Remove the item
if item in response["hits"]["hits"]:
2022-08-02 21:22:22 +00:00
# Anonymous
if is_anonymous:
2022-07-21 12:51:55 +00:00
response["hits"]["hits"].remove(item)
2022-08-02 21:22:22 +00:00
else:
if not user.is_superuser:
response["hits"]["hits"].remove(item)
else:
response["exemption"] = True
2022-07-21 12:51:55 +00:00
# Let the UI know something was redacted
response["redacted"] += 1
2022-07-21 12:52:41 +00:00
def run_main_query(client, user, query, custom_query=False, index=None, size=None):
2022-07-21 12:51:55 +00:00
"""
Low level helper to run an ES query.
Accept a user to pass it to the filter, so we can
avoid filtering for superusers.
Accept fields and size, for the fields we want to match and the
number of results to return.
"""
2022-07-21 12:52:41 +00:00
if not index:
index = settings.OPENSEARCH_INDEX_MAIN
if custom_query:
search_query = query
else:
search_query = construct_query(query, size)
2022-07-21 12:51:55 +00:00
try:
2022-07-21 12:52:41 +00:00
response = client.search(body=search_query, index=index)
2022-07-21 12:51:55 +00:00
except RequestError:
return False
filter_blacklisted(user, response)
return response
def query_results(request, size=None):
"""
API helper to alter the OpenSearch return format into something
a bit better to parse.
Accept a HTTP request object. Run the query, and annotate the
results with the other data we have.
"""
is_anonymous = isinstance(request.user, AnonymousUser)
if is_anonymous:
sizes = ["5", "10", "15", "20"]
else:
sizes = settings.OPENSEARCH_MAIN_SIZES
2022-07-21 12:51:55 +00:00
if not size:
if "size" in request.POST:
size = request.POST["size"]
if size not in sizes:
2022-07-21 12:51:55 +00:00
return False
if "query" in request.POST:
query = request.POST["query"]
2022-08-02 21:22:22 +00:00
if hasattr(request, "user"):
user = request.user
else:
user = None
2022-07-21 12:51:55 +00:00
results = run_main_query(
client,
2022-08-02 16:10:41 +00:00
user, # passed through run_main_query to filter_blacklisted
2022-07-21 12:51:55 +00:00
query,
size=size,
2022-07-21 12:51:55 +00:00
)
if not results:
return False
results_parsed = []
if "hits" in results.keys():
if "hits" in results["hits"]:
for item in results["hits"]["hits"]:
element = item["_source"]
element["id"] = item["_id"]
# Split the timestamp into date and time
ts = element["ts"]
ts_spl = ts.split("T")
date = ts_spl[0]
time = ts_spl[1]
element["date"] = date
element["time"] = time
results_parsed.append(element)
annotate_results(results_parsed)
context = {
"query": query,
"results": results_parsed,
"card": results["hits"]["total"]["value"],
"took": results["took"],
"redacted": results["redacted"],
"exemption": results["exemption"],
}
return context
def query_single_result(request):
context = query_results(request, 1)
dedup_set = {item["nick"] for item in context["results"]}
if dedup_set:
2022-07-21 12:51:55 +00:00
context["item"] = context["results"][0]
2022-07-21 12:51:55 +00:00
return (1, context)
def construct_query(query, size):
"""
Accept some query parameters and construct an OpenSearch query.
"""
if not size:
size = 5
2022-07-21 12:47:02 +00:00
query = {
"size": size,
2022-07-21 12:47:02 +00:00
"query": {
2022-07-21 12:49:17 +00:00
"query_string": {
2022-07-21 12:47:02 +00:00
"query": query,
2022-07-21 12:51:55 +00:00
# "fields": fields,
2022-07-21 12:49:17 +00:00
# "default_field": "msg",
# "type": "best_fields",
"fuzziness": "AUTO",
"fuzzy_transpositions": True,
"fuzzy_max_expansions": 50,
"fuzzy_prefix_length": 0,
2022-07-21 12:49:22 +00:00
# "minimum_should_match": 1,
2022-07-21 12:49:17 +00:00
"default_operator": "or",
"analyzer": "standard",
2022-07-21 12:49:22 +00:00
"lenient": True,
2022-07-21 12:49:17 +00:00
"boost": 1,
"allow_leading_wildcard": True,
2022-07-21 12:49:22 +00:00
# "enable_position_increments": False,
2022-07-21 12:49:17 +00:00
"phrase_slop": 3,
2022-07-21 12:49:22 +00:00
# "max_determinized_states": 10000,
2022-07-21 12:49:17 +00:00
"quote_field_suffix": "",
"quote_analyzer": "standard",
2022-07-21 12:49:22 +00:00
"analyze_wildcard": False,
2022-07-21 12:49:17 +00:00
"auto_generate_synonyms_phrase_query": True,
2022-07-21 12:47:02 +00:00
}
},
2022-07-21 12:51:38 +00:00
"sort": [
{
"ts": {
"order": "desc",
}
}
],
2022-07-21 12:47:02 +00:00
}
return query