neptune/core/views/ui/drilldown.py

504 lines
16 KiB
Python
Raw Normal View History

2022-07-27 19:53:41 +00:00
import json
import urllib
import uuid
2022-07-27 19:53:41 +00:00
from django.conf import settings
from django.http import HttpResponse, JsonResponse
2022-07-21 12:51:55 +00:00
from django.shortcuts import render
from django.urls import reverse
2022-07-21 12:51:55 +00:00
from django.views import View
from django_tables2 import SingleTableView
2022-07-21 12:51:55 +00:00
from rest_framework.parsers import FormParser
from rest_framework.views import APIView
2022-08-09 06:20:30 +00:00
from core.lib.context import construct_query
2022-09-06 10:53:32 +00:00
# from core.lib.opensearch import query_results
2022-09-05 21:57:20 +00:00
from core.lib.manticore import query_results
2022-07-21 12:51:55 +00:00
from core.lib.threshold import (
annotate_num_chans,
annotate_num_users,
get_chans,
get_users,
)
2022-08-09 08:04:31 +00:00
from core.views.ui.tables import DrilldownTable
2022-08-26 06:20:30 +00:00
# from copy import deepcopy
2022-08-09 08:04:31 +00:00
def parse_dates(dates):
spl = dates.split(" - ")
if all(spl):
spl = [f"{x.replace(' ', 'T')}" for x in spl]
if not len(spl) == 2:
message = "Invalid dates"
message_class = "danger"
return {"message": message, "class": message_class}
from_ts, to_ts = spl
from_date, from_time = from_ts.split("T")
to_date, to_time = to_ts.split("T")
return {
"from_date": from_date,
"to_date": to_date,
"from_time": from_time,
"to_time": to_time,
}
def create_tags(query):
"""
Grab the tags out of the query and make a list
we can add to the Bulma tags element when the page loads.
"""
spl = query.split("AND")
spl = [x.strip() for x in spl if ":" in x]
spl = [x.replace('"', "") for x in spl]
2022-08-11 22:17:45 +00:00
tags = [f"{tag}: {elem}" for tag, elem in [x.split(":")[:2] for x in spl]]
2022-08-09 06:20:30 +00:00
return tags
def parse_tags(tags_pre):
"""
Parse the tags from the variable tags_pre.
"""
tags = {}
tags_spl = tags_pre.split(",")
if tags_spl:
for tag in tags_spl:
tag = tag.split(": ")
if len(tag) == 2:
key, val = tag
tags[key] = val
return tags
def make_table(context):
table = DrilldownTable(context["object_list"])
context["table"] = table
# del context["results"]
return context
def make_graph(results):
2022-08-27 16:47:33 +00:00
graph = []
for index, item in enumerate(results):
date = str(index)
graph.append(
{
2022-08-27 16:47:33 +00:00
"text": item.get("tokens", None)
or item.get("msg", None)
or item.get("id"),
"nick": item.get("nick", None),
2022-08-27 16:47:33 +00:00
"channel": item.get("channel", None),
"net": item.get("net", None),
"value": item.get("sentiment", None) or None,
2022-08-27 16:47:33 +00:00
"date": date,
}
2022-08-27 16:47:33 +00:00
)
return json.dumps(graph)
def drilldown_search(request, return_context=False, template=None):
extra_params = {}
if not template:
2022-08-26 06:20:30 +00:00
template_name = "widgets/table_results.html"
else:
template_name = template
if request.user.is_anonymous:
sizes = settings.OPENSEARCH_MAIN_SIZES_ANON
else:
sizes = settings.OPENSEARCH_MAIN_SIZES
if request.GET:
if not request.htmx:
template_name = "ui/drilldown/drilldown.html"
query_params = request.GET.dict()
elif request.POST:
query_params = request.POST.dict()
else:
template_name = "ui/drilldown/drilldown.html"
context = {"sizes": sizes, "unique": "results"}
return render(request, template_name, context)
tmp_post = request.POST.dict()
tmp_get = request.GET.dict()
tmp_post = {k: v for k, v in tmp_post.items() if v and not v == "None"}
tmp_get = {k: v for k, v in tmp_get.items() if v and not v == "None"}
query_params.update(tmp_post)
query_params.update(tmp_get)
# URI we're passing to the template for linking
if "csrfmiddlewaretoken" in query_params:
del query_params["csrfmiddlewaretoken"]
# Parse the dates
if "dates" in query_params:
dates = parse_dates(query_params["dates"])
del query_params["dates"]
2022-08-09 06:20:30 +00:00
if dates:
if "message" in dates:
return render(request, template_name, dates)
query_params["from_date"] = dates["from_date"]
query_params["to_date"] = dates["to_date"]
query_params["from_time"] = dates["from_time"]
query_params["to_time"] = dates["to_time"]
2022-08-09 06:20:30 +00:00
if "query" in query_params:
# Remove null values
if query_params["query"] == "":
del query_params["query"]
# Turn the query into tags for populating the taglist
# tags = create_tags(query_params["query"])
# context["tags"] = tags
# else:
# context = {"object_list": []}
# Remove null values
if "query_full" in query_params:
if query_params["query_full"] == "":
del query_params["query_full"]
if "tags" in query_params:
if query_params["tags"] == "":
del query_params["tags"]
else:
tags = parse_tags(query_params["tags"])
extra_params["tags"] = tags
context = query_results(request, query_params, **extra_params)
context["unique"] = "results"
# Valid sizes
context["sizes"] = sizes
url_params = urllib.parse.urlencode(query_params)
context["client_uri"] = url_params
context["params"] = query_params
if "message" in context:
2022-09-05 21:57:20 +00:00
if return_context:
return context
response = render(request, template_name, context)
if request.GET:
if request.htmx:
response["HX-Push"] = reverse("home") + "?" + url_params
elif request.POST:
response["HX-Push"] = reverse("home") + "?" + url_params
return response
# Create data for chart.js sentiment graph
graph = make_graph(context["object_list"])
context["data"] = graph
context = make_table(context)
# URI we're passing to the template for linking, table fields removed
table_fields = ["page", "sort"]
clean_params = {k: v for k, v in query_params.items() if k not in table_fields}
clean_url_params = urllib.parse.urlencode(clean_params)
context["uri"] = clean_url_params
# Warn users trying to use query string that the simple query supersedes it
if all([x in query_params for x in ["query", "query_full"]]):
context["message"] = (
"You are searching with both query types. "
"The simple query will be used. "
"The full query will be ignored. "
"Remove the text from the simple query if you wish "
"to use the full query."
)
context["class"] = "warning"
2022-08-26 06:20:30 +00:00
# unique = str(uuid.uuid4())[:8]
response = render(request, template_name, context)
if request.GET:
if request.htmx:
response["HX-Push"] = reverse("home") + "?" + url_params
elif request.POST:
response["HX-Push"] = reverse("home") + "?" + url_params
if return_context:
return context
return response
class DrilldownTableView(SingleTableView):
table_class = DrilldownTable
2022-08-26 06:20:30 +00:00
template_name = "widgets/table_results.html"
2022-08-10 19:45:55 +00:00
paginate_by = settings.DRILLDOWN_RESULTS_PER_PAGE
def get_queryset(self, request, **kwargs):
context = drilldown_search(request, return_context=True)
# Save the context as we will need to merge other attributes later
self.context = context
2022-08-10 19:36:55 +00:00
if "object_list" in context:
return context["object_list"]
else:
return []
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset(request)
show = []
show = set().union(*(d.keys() for d in self.object_list))
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(
self.object_list, "exists"
):
is_empty = not self.object_list.exists() # noqa
else:
is_empty = not self.object_list # noqa
context = self.get_context_data()
if isinstance(self.context, HttpResponse):
return self.context
2022-08-10 19:36:55 +00:00
for k, v in self.context.items():
if k not in context:
context[k] = v
context["show"] = show
if request.method == "GET":
if not request.htmx:
self.template_name = "ui/drilldown/drilldown.html"
response = self.render_to_response(context)
# if not request.method == "GET":
if "client_uri" in context:
response["HX-Push"] = reverse("home") + "?" + context["client_uri"]
return response
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
2022-08-02 21:22:22 +00:00
class Drilldown(View):
template_name = "ui/drilldown/drilldown.html"
plan_name = "drilldown"
def get(self, request):
return drilldown_search(request)
2022-07-21 12:51:55 +00:00
def post(self, request):
return drilldown_search(request)
2022-07-21 12:51:55 +00:00
2022-08-12 08:08:44 +00:00
class DrilldownContextModal(APIView):
parser_classes = [FormParser]
plan_name = "drilldown"
template_name = "modals/context.html"
def post(self, request):
if request.resolver_match.url_name == "modal_context_table":
self.template_name = "partials/context_table.html"
2022-08-09 06:20:30 +00:00
2022-08-09 06:20:30 +00:00
size = 20
2022-08-16 18:43:55 +00:00
nicks_sensitive = None
2022-08-15 18:39:26 +00:00
query = False
2022-08-09 06:20:30 +00:00
# Create the query params from the POST arguments
2022-08-30 09:30:17 +00:00
mandatory = [
"net",
"channel",
"num",
"source",
"index",
"nick",
"type",
"mtype",
]
2022-08-09 06:20:30 +00:00
invalid = [None, False, "", "None"]
2022-08-09 06:20:30 +00:00
query_params = {k: v for k, v in request.data.items() if v}
for key in query_params:
if query_params[key] in invalid:
query_params[key] = None
for key in mandatory:
if key not in query_params:
query_params[key] = None
2022-08-18 06:20:30 +00:00
# Lookup the hash values but don't disclose them to the user
2022-08-26 06:20:30 +00:00
# if settings.HASHING:
# if query_params["source"] not in settings.SAFE_SOURCES:
# SAFE_PARAMS = deepcopy(query_params)
# hash_lookup(request.user, SAFE_PARAMS)
# else:
# SAFE_PARAMS = deepcopy(query_params)
# else:
# SAFE_PARAMS = query_params
2022-08-18 06:20:30 +00:00
type = None
2022-08-03 06:20:30 +00:00
if request.user.is_superuser:
2022-08-16 18:43:55 +00:00
if "type" in query_params:
type = query_params["type"]
2022-08-15 18:39:26 +00:00
if type == "znc":
2022-08-16 18:43:55 +00:00
query_params["channel"] = "*status"
2022-08-26 06:20:30 +00:00
# SAFE_PARAMS["channel"] = "*status"
2022-08-15 18:39:26 +00:00
if type in ["query", "notice"]:
2022-08-16 18:43:55 +00:00
nicks_sensitive = [
2022-08-26 06:20:30 +00:00
query_params["channel"],
query_params["nick"],
2022-08-16 18:43:55 +00:00
] # UNSAFE
# nicks = [query_params["channel"], query_params["nick"]]
2022-08-15 18:39:26 +00:00
query = True
if (
2022-08-16 18:43:55 +00:00
query_params["index"] == "int"
and query_params["mtype"] == "msg"
2022-08-15 18:39:26 +00:00
and not type == "query"
):
2022-08-16 18:43:55 +00:00
query_params["index"] = "main"
2022-08-26 06:20:30 +00:00
# SAFE_PARAMS["index"] = "main"
2022-08-15 18:39:26 +00:00
2022-08-16 18:43:55 +00:00
if query_params["type"] in ["znc", "auth"]:
2022-08-15 18:39:26 +00:00
query = True
if not request.user.is_superuser:
2022-08-16 18:43:55 +00:00
query_params["index"] = "main"
2022-08-26 06:20:30 +00:00
# SAFE_PARAMS["index"] = "main"
2022-08-15 18:39:26 +00:00
2022-08-16 18:43:55 +00:00
query_params["sorting"] = "desc"
2022-08-26 06:20:30 +00:00
# SAFE_PARAMS["sorting"] = "desc"
2022-08-15 16:59:09 +00:00
2022-08-18 06:20:30 +00:00
annotate = False
2022-08-30 09:30:17 +00:00
if query_params["source"] == "irc":
2022-08-16 18:43:55 +00:00
if query_params["type"] not in ["znc", "auth"]:
2022-08-18 06:20:30 +00:00
annotate = True
2022-08-09 06:20:30 +00:00
# Create the query with the context helper
2022-09-06 10:53:32 +00:00
if query_params["num"].isdigit():
query_params["num"] = int(query_params["num"])
2022-08-09 06:20:30 +00:00
search_query = construct_query(
2022-08-16 18:43:55 +00:00
query_params["index"],
2022-08-26 06:20:30 +00:00
query_params["net"],
query_params["channel"],
2022-08-30 09:30:17 +00:00
query_params["source"],
2022-08-26 06:20:30 +00:00
query_params["num"],
2022-08-09 06:20:30 +00:00
size,
2022-08-15 16:59:09 +00:00
type=type,
2022-08-16 18:43:55 +00:00
nicks=nicks_sensitive,
2022-08-09 06:20:30 +00:00
)
2022-08-18 06:20:30 +00:00
2022-08-09 06:20:30 +00:00
results = query_results(
request,
2022-08-26 06:20:30 +00:00
query_params,
annotate=annotate,
custom_query=search_query,
reverse=True,
dedup_fields=["net", "type", "msg"],
2022-08-09 06:20:30 +00:00
)
if "message" in results:
return render(request, self.template_name, results)
2022-08-26 06:20:30 +00:00
# if settings.HASHING: # we probably want to see the tokens
# if query_params["source"] not in settings.SAFE_SOURCES:
# if not request.user.has_perm("core.bypass_hashing"):
# for index, item in enumerate(results["object_list"]):
# if "tokens" in item:
# results["object_list"][index]["msg"] = results[
# "object_list"
# ][index].pop("tokens")
# # item["msg"] = item.pop("tokens")
2022-08-16 18:43:55 +00:00
2022-08-09 06:20:30 +00:00
# Make the time nicer
# for index, item in enumerate(results["object_list"]):
# results["object_list"][index]["time"] = item["time"]+"SSS"
unique = str(uuid.uuid4())[:8]
2022-09-06 10:53:32 +00:00
print("PARAMS", query_params)
2022-08-12 08:08:44 +00:00
context = {
2022-08-09 06:20:30 +00:00
"net": query_params["net"],
"channel": query_params["channel"],
2022-08-30 09:30:17 +00:00
"source": query_params["source"],
2022-08-09 06:20:30 +00:00
"ts": f"{query_params['date']} {query_params['time']}",
2022-08-09 06:20:30 +00:00
"object_list": results["object_list"],
2022-08-09 06:20:30 +00:00
"time": query_params["time"],
"date": query_params["date"],
"index": query_params["index"],
"num": query_params["num"],
"type": query_params["type"],
"mtype": query_params["mtype"],
2022-08-03 06:20:30 +00:00
"nick": query_params["nick"],
"params": query_params,
"unique": unique,
2022-08-12 08:08:44 +00:00
}
2022-08-15 18:39:26 +00:00
if request.user.is_superuser:
if query:
context["query"] = True
2022-08-09 06:20:30 +00:00
2022-08-12 08:08:44 +00:00
return render(request, self.template_name, context)
2022-08-02 21:22:22 +00:00
class ThresholdInfoModal(APIView):
2022-07-21 12:51:55 +00:00
parser_classes = [FormParser]
plan_name = "drilldown"
template_name = "modals/drilldown.html"
2022-08-28 19:26:15 +00:00
def post(self, request, type=None):
2022-08-02 16:10:41 +00:00
# if not request.user.has_plan(self.plan_name):
2022-08-02 21:22:22 +00:00
# return JsonResponse({"success": False})
2022-07-21 12:51:55 +00:00
if "net" not in request.data:
return JsonResponse({"success": False})
if "nick" not in request.data:
return JsonResponse({"success": False})
if "channel" not in request.data:
return JsonResponse({"success": False})
2022-08-28 19:26:15 +00:00
if type == "window":
self.template_name = "windows/drilldown.html"
2022-08-29 11:24:06 +00:00
elif type == "widget":
self.template_name = "widgets/drilldown.html"
2022-08-18 06:20:30 +00:00
2022-07-21 12:51:55 +00:00
net = request.data["net"]
nick = request.data["nick"]
channel = request.data["channel"]
2022-08-18 06:20:30 +00:00
# SAFE BLOCK #
# Lookup the hash values but don't disclose them to the user
2022-08-26 06:20:30 +00:00
# if settings.HASHING:
# SAFE_PARAMS = request.data.dict()
# hash_lookup(request.user, SAFE_PARAMS)
channels = get_chans(net, [nick])
users = get_users(net, [nick])
num_users = annotate_num_users(net, channels)
num_chans = annotate_num_chans(net, users)
2022-07-21 12:51:55 +00:00
if channels:
2022-08-26 06:20:30 +00:00
inter_users = get_users(net, channels)
2022-07-21 12:51:55 +00:00
else:
inter_users = []
if users:
2022-08-26 06:20:30 +00:00
inter_chans = get_chans(net, users)
2022-07-21 12:51:55 +00:00
else:
inter_chans = []
2022-08-26 06:20:30 +00:00
# if settings.HASHING:
# hash_list(request.user, inter_chans)
# hash_list(request.user, inter_users)
2022-08-27 12:18:24 +00:00
2022-08-26 06:20:30 +00:00
# hash_list(request.user, num_chans, hash_keys=True)
# hash_list(request.user, num_users, hash_keys=True)
2022-08-18 06:20:30 +00:00
2022-08-26 06:20:30 +00:00
# hash_list(request.user, channels)
# hash_list(request.user, users)
2022-08-18 06:20:30 +00:00
2022-08-26 06:20:30 +00:00
# if settings.RANDOMISATION:
# randomise_list(request.user, num_chans)
# randomise_list(request.user, num_users)
2022-08-18 06:20:30 +00:00
# SAFE BLOCK END #
2022-08-16 18:43:55 +00:00
unique = str(uuid.uuid4())[:8]
2022-07-21 12:51:55 +00:00
context = {
"net": net,
"nick": nick,
"channel": channel,
"chans": channels,
"users": users,
"inter_chans": inter_chans,
"inter_users": inter_users,
"num_users": num_users,
"num_chans": num_chans,
"unique": unique,
2022-07-21 12:51:55 +00:00
}
return render(request, self.template_name, context)