failmap/admin

View on GitHub
websecmap/map/views.py

Summary

Maintainability
D
1 day
Test Coverage
import json
import logging
from datetime import datetime
from wsgiref.util import FileWrapper

import django_excel as excel
import pytz
from django.conf import settings
from django.contrib.auth.decorators import user_passes_test
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils.text import slugify
from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_page

from websecmap import __version__
from websecmap.app.common import JSEncoder
from websecmap.app.constance import get_bulk_values
from websecmap.map.logic import datasets
from websecmap.map.logic.admin import (
    add_organization,
    add_proxies,
    add_urls,
    switch_lattitude_and_longitude,
    user_is_staff_member,
)
from websecmap.map.logic.datasets import create_filename
from websecmap.map.logic.explain import explain, get_all_explains, get_recent_explains, remove_explanation
from websecmap.map.logic.improvements import get_improvements
from websecmap.map.logic.latest import get_all_latest_scans
from websecmap.map.logic.map import get_map_data
from websecmap.map.logic.map_defaults import (
    DEFAULT_COUNTRY,
    DEFAULT_LAYER,
    get_country,
    get_defaults,
    get_initial_countries,
    get_layers,
    get_organization_type,
)
from websecmap.map.logic.map_health import get_lastest_map_health_data
from websecmap.map.logic.organization_report import get_organization_report_by_id, get_organization_report_by_name
from websecmap.map.logic.rss_feeds import latest_updates
from websecmap.map.logic.stats_and_graphs import (
    get_organization_vulnerability_timeline,
    get_organization_vulnerability_timeline_via_name,
    get_short_and_simple_stats,
    get_stats,
    get_vulnerability_graph,
    what_to_improve,
)
from websecmap.map.logic.ticker import get_ticker_data
from websecmap.map.logic.top import get_top_fail_data, get_top_win_data
from websecmap.map.logic.upcoming_scans import get_next_and_last_scans
from websecmap.map.models import Configuration
from websecmap.organizations.models import Organization
from websecmap.scanners import plannedscan
from websecmap.scanners.models import Screenshot

log = logging.getLogger(__package__)

one_minute = 60
one_hour = 60 * 60
four_hours = 60 * 60 * 4
one_day = 24 * 60 * 60
ten_minutes = 60 * 10

DEFAULT_FILE_FORMAT = "json"


def empty_response():
    return JsonResponse({}, encoder=JSEncoder)


def generic_download(filename, data, file_type):
    supported_types = {
        "xlsx": {"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
        "ods": {"content_type": "application/vnd.oasis.opendocument.spreadsheet"},
        "csv": {"content_type": "text/csv"},
        "json": {"content_type": "text/json"},
        "mediawiki": {"content_type": "text/plain"},
        "latex": {"content_type": "text/plain"},
    }

    if file_type in supported_types:
        http_response = excel.make_response(data, file_type)
        http_response["Content-Disposition"] = "attachment; filename=%s.%s" % (slugify(filename), file_type)
        http_response["Content-type"] = supported_types[file_type]["content_type"]
        return http_response

    return JsonResponse({}, encoder=JSEncoder)


def defaults(request):
    return JsonResponse(get_defaults(), encoder=JSEncoder, safe=False)


def layers(request, country):
    return JsonResponse(get_layers(country), encoder=JSEncoder, safe=False)


@cache_page(one_day)
def export_urls_only(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, file_format: str = DEFAULT_FILE_FORMAT
):
    data = datasets.export_urls_only(country, organization_type)
    filename = create_filename("urls_only", country, organization_type)

    return generic_download(filename, data, file_format)


@cache_page(one_day)
def export_organizations(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, file_format: str = DEFAULT_FILE_FORMAT
):
    data = datasets.export_organizations(country, organization_type)
    filename = create_filename("organizations", country, organization_type)
    return generic_download(filename, data, file_format)


@cache_page(one_day)
def export_organization_types(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, file_format: str = DEFAULT_FILE_FORMAT
):
    data = datasets.export_organization_types()
    filename = create_filename("organization_types", country, organization_type)
    return generic_download(filename, data, file_format)


@cache_page(one_day)
def export_coordinates(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, file_format: str = DEFAULT_FILE_FORMAT
):
    data = datasets.export_coordinates(country, organization_type)
    filename = create_filename("coordinates", country, organization_type)
    return generic_download(filename, data, file_format)


@cache_page(one_day)
def export_urls(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, file_format: str = DEFAULT_FILE_FORMAT
):
    data = datasets.export_urls(country, organization_type)
    filename = create_filename("urls", country, organization_type)
    return generic_download(filename, data, file_format)


@cache_page(one_hour)
def export_explains(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, file_format: str = DEFAULT_FILE_FORMAT
):
    # we cannot use the queryset, as explains are two querysets (or a complex query)
    # so we currently offer json in any case, even if the site says otherwise.
    data = get_all_explains(country, organization_type)
    filename = create_filename("explains", country, organization_type)
    response = JsonResponse(
        data,
        safe=False,
        encoder=JSEncoder,
    )
    response["Content-Disposition"] = 'attachment; filename="%s.json"' % filename
    return response


def inject_default_language_cookie(request, response) -> HttpResponse:
    # If you visit any of the main pages, this is set to the desired language your browser emits.
    # This synchronizes the language between javascript (OS language) and browser (Accept Language).
    if "preferred_language" not in request.COOKIES:
        # Get the accept language,
        # Add the cookie to render.
        accept_language = request.LANGUAGE_CODE[0:2]  # only support 'en' not 'en-gb'
        response.set_cookie(key="preferred_language", value=accept_language)

    return response


def emptypage(request):
    return render(request, "map/empty.html")


def countries_and_layers():
    confs = Configuration.objects.all().filter(is_displayed=True).order_by("display_order")

    # returns this, translated according to the preferred locale, this should be JS in the future:
    countries = {
        "EN": {
            "name": "",
            "flag": "",
            "code": "EN",
            "layers": [],
        },
    }

    # use django countries to augment this infromation based on the current prefered language,
    # which unfortunately doesn't change when switching language, but ok... for now it's fine.
    countries = {}
    for conf in confs:
        if conf.country.code not in countries:
            countries = {
                **countries,
                **{
                    conf.country.code: {
                        "code": conf.country.code,
                        "name": conf.country.name,
                        "flag": conf.country.flag,
                        "layers": [conf.organization_type.name],
                    }
                },
            }
        else:
            countries[conf.country]["layers"].append(conf.organization_type.name)

    return countries


@cache_page(one_hour)
def config(request):
    configuration = get_bulk_values(
        [
            "PROJECT_COUNTRY",
            "PROJECT_NAME",
            "PROJECT_TAGLINE",
            "SHOW_INTRO",
            "SHOW_CHARTS",
            "SHOW_COMPLY_OR_EXPLAIN",
            "SHOW_SCAN_SCHEDULE",
            "SHOW_DATASETS",
            "SHOW_ANNOUNCEMENT",
            "SHOW_EXTENSIVE_STATISTICS",
            "SHOW_STATS_NUMBERS",
            "SHOW_STATS_IMPROVEMENTS",
            "SHOW_STATS_GRAPHS",
            "SHOW_STATS_CHANGES",
            "SHOW_TICKER",
            "SHOW_SERVICES",
            "SHOW_FTP",
            "SHOW_PLAIN_HTTPS",
            "SHOW_DNSSEC",
            "PLUS_SHOW_INFO",
            "SHOW_HTTP_SECURITY_HEADER_STRICT_TRANSPORT_SECURITY",
            "SHOW_HTTP_SECURITY_HEADER_X_CONTENT_TYPE_OPTIONS",
            "SHOW_HTTP_SECURITY_HEADER_X_FRAME_OPTIONS",
            "SHOW_HTTP_SECURITY_HEADER_X_XSS_PROTECTION",
            "SHOW_TLS_QUALYS_CERTIFICATE_TRUSTED",
            "SHOW_TLS_QUALYS_ENCRYPTION_QUALITY",
            "SHOW_INTERNET_NL_MAIL_STARTTLS_TLS_AVAILABLE",
            "SHOW_INTERNET_NL_MAIL_AUTH_SPF_EXIST",
            "SHOW_INTERNET_NL_MAIL_AUTH_DKIM_EXIST",
            "SHOW_INTERNET_NL_MAIL_AUTH_DMARC_EXIST",
            "RESPONSIBLE_ORGANIZATION_NAME",
            "RESPONSIBLE_ORGANIZATION_PROMO_TEXT",
            "RESPONSIBLE_ORGANIZATION_WEBSITE",
            "RESPONSIBLE_ORGANIZATION_MAIL",
            "RESPONSIBLE_ORGANIZATION_TWITTER",
            "RESPONSIBLE_ORGANIZATION_FACEBOOK",
            "RESPONSIBLE_ORGANIZATION_LINKEDIN",
            "RESPONSIBLE_ORGANIZATION_WHATSAPP",
            "RESPONSIBLE_ORGANIZATION_PHONE",
            "PROJECT_NAME",
            "PROJECT_TAGLINE",
            "PROJECT_COUNTRY",
            "PROJECT_MAIL",
            "PROJECT_ISSUE_MAIL",
            "PROJECT_TWITTER",
            "PROJECT_FACEBOOK",
            "COMPLY_OR_EXPLAIN_DISCUSSION_FORUM_LINK",
            "COMPLY_OR_EXPLAIN_EMAIL_ADDRESS",
            "MAPBOX_ACCESS_TOKEN",
            "GITTER_CHAT_ENABLE",
            "GITTER_CHAT_CHANNEL",
            "ANNOUNCEMENT",
        ]
    )

    config_object = {
        "show": {
            "intro": configuration["SHOW_INTRO"],
            "charts": configuration["SHOW_CHARTS"],
            "comply_or_explain": configuration["SHOW_COMPLY_OR_EXPLAIN"],
            "scan_schedule": configuration["SHOW_SCAN_SCHEDULE"],
            "datasets": configuration["SHOW_DATASETS"],
            "announcement": configuration["SHOW_ANNOUNCEMENT"],
            "statistics": configuration["SHOW_EXTENSIVE_STATISTICS"],
            "numbers": configuration["SHOW_STATS_NUMBERS"],
            "improvements": configuration["SHOW_STATS_IMPROVEMENTS"],
            "graphs": configuration["SHOW_STATS_GRAPHS"],
            "changes": configuration["SHOW_STATS_CHANGES"],
            "ticker": configuration["SHOW_TICKER"],
            "services": configuration["SHOW_SERVICES"],
            "plus_info": configuration["PLUS_SHOW_INFO"],
            "issues": {
                "ftp": configuration["SHOW_FTP"],
                "plain_https": configuration["SHOW_PLAIN_HTTPS"],
                "dnssec": configuration["SHOW_DNSSEC"],
                "http_security_header_strict_transport_security": configuration[
                    "SHOW_HTTP_SECURITY_HEADER_STRICT_TRANSPORT_SECURITY"
                ],
                "http_security_header_x_content_type_options": configuration[
                    "SHOW_HTTP_SECURITY_HEADER_X_CONTENT_TYPE_OPTIONS"
                ],
                "http_security_header_x_frame_options": configuration["SHOW_HTTP_SECURITY_HEADER_X_FRAME_OPTIONS"],
                "http_security_header_x_xss_protection": configuration["SHOW_HTTP_SECURITY_HEADER_X_XSS_PROTECTION"],
                "tls_qualys_certificate_trusted": configuration["SHOW_TLS_QUALYS_CERTIFICATE_TRUSTED"],
                "tls_qualys_encryption_quality": configuration["SHOW_TLS_QUALYS_ENCRYPTION_QUALITY"],
                "internet_nl_mail_starttls_tls_available": configuration[
                    "SHOW_INTERNET_NL_MAIL_STARTTLS_TLS_AVAILABLE"
                ],
                "internet_nl_mail_auth_spf_exist": configuration["SHOW_INTERNET_NL_MAIL_AUTH_SPF_EXIST"],
                "internet_nl_mail_auth_dkim_exist": configuration["SHOW_INTERNET_NL_MAIL_AUTH_DKIM_EXIST"],
                "internet_nl_mail_auth_dmarc_exist": configuration["SHOW_INTERNET_NL_MAIL_AUTH_DMARC_EXIST"],
            },
        },
        "announcement": configuration["ANNOUNCEMENT"],
        "responsible_organization": {
            "name": configuration["RESPONSIBLE_ORGANIZATION_NAME"],
            "promo_text": configuration["RESPONSIBLE_ORGANIZATION_PROMO_TEXT"],
            "website": configuration["RESPONSIBLE_ORGANIZATION_WEBSITE"],
            "mail": configuration["RESPONSIBLE_ORGANIZATION_MAIL"],
            "twitter": configuration["RESPONSIBLE_ORGANIZATION_TWITTER"],
            "facebook": configuration["RESPONSIBLE_ORGANIZATION_FACEBOOK"],
            "linkedin": configuration["RESPONSIBLE_ORGANIZATION_LINKEDIN"],
            "whatsapp": configuration["RESPONSIBLE_ORGANIZATION_WHATSAPP"],
            "phone": configuration["RESPONSIBLE_ORGANIZATION_PHONE"],
        },
        "project": {
            "name": configuration["PROJECT_NAME"],
            "tagline": configuration["PROJECT_TAGLINE"],
            "country": configuration["PROJECT_COUNTRY"],
            "mail": configuration["PROJECT_MAIL"],
            "issue_mail": configuration["PROJECT_ISSUE_MAIL"],
            "twitter": configuration["PROJECT_TWITTER"],
            "facebook": configuration["PROJECT_FACEBOOK"],
        },
        "comply_or_explain": {
            "forum_link": configuration["COMPLY_OR_EXPLAIN_DISCUSSION_FORUM_LINK"],
            "email_address": configuration["COMPLY_OR_EXPLAIN_EMAIL_ADDRESS"],
        },
        "debug": True if settings.DEBUG else False,
        "admin": settings.ADMIN,
        "country_and_layers": countries_and_layers(),
    }

    return JsonResponse(config_object, encoder=JSEncoder)


@cache_page(one_hour)
def index(request, map_configuration=None):

    initial_countries = get_initial_countries()

    if map_configuration:
        map_defaults = {"country": map_configuration.country, "layer": map_configuration.organization_type.name}
    else:
        map_defaults = get_defaults()

    # instead of asking for every config variable, get all of them in one go
    config = get_bulk_values(
        [
            "PROJECT_COUNTRY",
            "PROJECT_NAME",
            "PROJECT_TAGLINE",
            "SHOW_INTRO",
            "SHOW_CHARTS",
            "SHOW_COMPLY_OR_EXPLAIN",
            "SHOW_SCAN_SCHEDULE",
            "SHOW_DATASETS",
            "SHOW_ANNOUNCEMENT",
            "SHOW_EXTENSIVE_STATISTICS",
            "SHOW_STATS_NUMBERS",
            "SHOW_STATS_IMPROVEMENTS",
            "SHOW_STATS_GRAPHS",
            "SHOW_STATS_CHANGES",
            "SHOW_TICKER",
            "SHOW_SERVICES",
            "SHOW_FTP",
            "SHOW_PLAIN_HTTPS",
            "SHOW_DNSSEC",
            "PLUS_SHOW_INFO",
            "SHOW_HTTP_SECURITY_HEADER_STRICT_TRANSPORT_SECURITY",
            "SHOW_HTTP_SECURITY_HEADER_X_CONTENT_TYPE_OPTIONS",
            "SHOW_HTTP_SECURITY_HEADER_X_FRAME_OPTIONS",
            "SHOW_HTTP_SECURITY_HEADER_X_XSS_PROTECTION",
            "SHOW_TLS_QUALYS_CERTIFICATE_TRUSTED",
            "SHOW_TLS_QUALYS_ENCRYPTION_QUALITY",
            "SHOW_INTERNET_NL_MAIL_STARTTLS_TLS_AVAILABLE",
            "SHOW_INTERNET_NL_MAIL_AUTH_SPF_EXIST",
            "SHOW_INTERNET_NL_MAIL_AUTH_DKIM_EXIST",
            "SHOW_INTERNET_NL_MAIL_AUTH_DMARC_EXIST",
            "RESPONSIBLE_ORGANIZATION_NAME",
            "RESPONSIBLE_ORGANIZATION_PROMO_TEXT",
            "RESPONSIBLE_ORGANIZATION_WEBSITE",
            "RESPONSIBLE_ORGANIZATION_MAIL",
            "RESPONSIBLE_ORGANIZATION_TWITTER",
            "RESPONSIBLE_ORGANIZATION_FACEBOOK",
            "RESPONSIBLE_ORGANIZATION_LINKEDIN",
            "RESPONSIBLE_ORGANIZATION_WHATSAPP",
            "RESPONSIBLE_ORGANIZATION_PHONE",
            "PROJECT_NAME",
            "PROJECT_TAGLINE",
            "PROJECT_COUNTRY",
            "PROJECT_MAIL",
            "PROJECT_ISSUE_MAIL",
            "PROJECT_TWITTER",
            "PROJECT_FACEBOOK",
            "COMPLY_OR_EXPLAIN_DISCUSSION_FORUM_LINK",
            "COMPLY_OR_EXPLAIN_EMAIL_ADDRESS",
            "MAPBOX_ACCESS_TOKEN",
            "GITTER_CHAT_ENABLE",
            "GITTER_CHAT_CHANNEL",
            "ANNOUNCEMENT",
        ]
    )

    # a number of variables are injected so they can be used inside javascript.
    return inject_default_language_cookie(
        request,
        render(
            request,
            "map/index.html",
            {
                "configuration": config,
                "version": __version__,
                "admin": settings.ADMIN,
                "sentry_token": settings.SENTRY_TOKEN,
                "country": config["PROJECT_COUNTRY"],
                "debug": True if settings.DEBUG else False,
                "language": request.LANGUAGE_CODE[0:2],  # only support 'en' not 'en-gb',
                "timestamp": datetime.now(pytz.UTC).isoformat(),
                "initial_map_data_url": "",
                "initial_countries": initial_countries,
                "countries_and_layers": countries_and_layers(),
                "default_country": map_defaults["country"],
                "default_layer": map_defaults["layer"],
                "default_week": 0,
                "number_of_countries": len(initial_countries),
                "initial_map_data": get_map_data(map_defaults["country"], map_defaults["layer"], 0, ""),
            },
        ),
    )


@cache_page(one_day)
def robots_txt(request):
    return render(request, "map/robots.txt", content_type="text/plain")


@cache_page(one_day)
def security_txt(request):
    return render(request, "map/security.txt", content_type="text/plain")


@cache_page(one_day)
def manifest_json(request):
    # App manifest
    # https://developer.chrome.com/apps/manifest
    # https://developer.mozilla.org/en-US/docs/Web/Manifest
    manifest = {
        "name": _("Site Title"),
        "short_name": _("Site Title"),
        "description": _("Introduction"),
        "version": __version__,
        "manifest_version": 3,
        "start_url": ".",
        "display": "standalone",
        "background_color": "#fff",
        "orientation": "any",
        "icons": [
            {"src": "static/favicons/android-icon-36x36.png", "sizes": "36x36", "type": "image/png", "density": "0.75"},
            {"src": "static/favicons/android-icon-48x48.png", "sizes": "48x48", "type": "image/png", "density": "1.0"},
            {"src": "static/favicons/android-icon-72x72.png", "sizes": "72x72", "type": "image/png", "density": "1.5"},
            {"src": "static/favicons/android-icon-96x96.png", "sizes": "96x96", "type": "image/png", "density": "2.0"},
            {
                "src": "static/favicons/android-icon-144x144.png",
                "sizes": "144x144",
                "type": "image/png",
                "density": "3.0",
            },
            {
                "src": "static/favicons/android-icon-192x192.png",
                "sizes": "192x192",
                "type": "image/png",
                "density": "4.0",
            },
        ],
    }
    return JsonResponse(manifest, encoder=JSEncoder)


@cache_page(one_hour)
def organization_report_by_id(
    request, country: str = DEFAULT_COUNTRY, organization_type: str = DEFAULT_LAYER, organization_id=None, weeks_back=0
):
    if not organization_id:
        return empty_response()

    report = get_organization_report_by_id(organization_id, weeks_back)
    return JsonResponse(report, safe=False, encoder=JSEncoder)


@cache_page(one_hour)
def organization_report_by_name(
    request,
    country: str = DEFAULT_COUNTRY,
    organization_type: str = DEFAULT_LAYER,
    organization_name=None,
    weeks_back=0,
):
    if not (organization_name and country and organization_type):
        return empty_response()

    report = get_organization_report_by_name(
        organization_name=organization_name, country=country, organization_type=organization_type, weeks_back=weeks_back
    )
    return JsonResponse(report, safe=False, encoder=JSEncoder)


@cache_page(one_hour)
def top_fail(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, weeks_back=0):
    data = get_top_fail_data(country, organization_type, weeks_back)
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@cache_page(one_hour)
def top_win(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, weeks_back=0):
    data = get_top_win_data(country, organization_type, weeks_back)
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@cache_page(one_hour)
def stats(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, weeks_back=0):
    reports = get_stats(country, organization_type, weeks_back)
    return JsonResponse(reports, encoder=JSEncoder)


@cache_page(one_hour)
def _what_to_improve(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, issue_type=""):
    reports = what_to_improve(country, organization_type, issue_type)
    return JsonResponse(reports, encoder=JSEncoder, safe=False)


@cache_page(one_hour)
def get_short_and_simple_stats_(request, weeks_back=0):
    data = get_short_and_simple_stats(weeks_back)
    return JsonResponse(data, encoder=JSEncoder)


@cache_page(one_hour)
def organization_vulnerability_timeline(request, organization_id: int, organization_type: str = "", country: str = ""):
    stats = get_organization_vulnerability_timeline(organization_id)
    return JsonResponse(stats, encoder=JSEncoder, safe=False)


@cache_page(one_hour)
def organization_vulnerability_timeline_via_name(
    request, organization_name: str, organization_type: str = "", country: str = ""
):
    stats = get_organization_vulnerability_timeline_via_name(organization_name, organization_type, country)
    return JsonResponse(stats, encoder=JSEncoder, safe=False)


@cache_page(one_hour)
def vulnerability_graphs(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, weeks_back=0):
    stats = get_vulnerability_graph(country, organization_type, weeks_back)
    return JsonResponse(stats, encoder=JSEncoder)


@cache_page(ten_minutes)
def improvements(
    request,
    country: str = DEFAULT_COUNTRY,
    organization_type: str = DEFAULT_LAYER,
    weeks_back: int = 0,
    weeks_duration: int = 0,
):
    changes = get_improvements(country, organization_type, weeks_back, weeks_duration)
    return JsonResponse(changes, encoder=JSEncoder)


@cache_page(one_hour)
def ticker(
    request,
    country: str = DEFAULT_COUNTRY,
    organization_type: str = DEFAULT_LAYER,
    weeks_back: int = 0,
    weeks_duration: int = 0,
):
    data = get_ticker_data(country, organization_type, weeks_back, weeks_duration)
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@cache_page(four_hours)
def map_default(request, days_back: int = 0, displayed_issue: str = "all"):
    defaults = (
        Configuration.objects.all()
        .filter(is_displayed=True, is_the_default_option=True)
        .order_by("display_order")
        .values("country", "organization_type__name")
        .first()
    )

    # On an empty database, just get the Netherlands.
    if not defaults:
        return map_data(request, DEFAULT_COUNTRY, DEFAULT_LAYER, days_back, displayed_issue)

    return map_data(request, defaults["country"], defaults["organization_type__name"], days_back, displayed_issue)


@cache_page(one_hour)
def organization_list(request, country: str = DEFAULT_COUNTRY, organization_type: str = DEFAULT_LAYER):
    query = (
        Organization.objects.all()
        .filter(country=get_country(country), type=get_organization_type(organization_type), is_dead=False)
        .values_list(
            "id",
            "name",
            "computed_name_slug",
        )
    )

    data = [{"id": elem[0], "name": elem[1], "slug": elem[2]} for elem in query]

    return JsonResponse(data, encoder=JSEncoder, safe=False)


@cache_page(four_hours)
def map_data(
    request,
    country: str = DEFAULT_COUNTRY,
    organization_type: str = DEFAULT_LAYER,
    days_back: int = 0,
    displayed_issue: str = "all",
):
    data = get_map_data(country, organization_type, days_back, displayed_issue)

    return JsonResponse(data, encoder=JSEncoder)


@cache_page(ten_minutes)
def all_latest_scans(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER):
    dataset = get_all_latest_scans(country, organization_type)
    return JsonResponse(dataset, encoder=JSEncoder)


@cache_page(ten_minutes)
def explain_list(request, country, organization_type):
    data = get_recent_explains(country, organization_type)
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@cache_page(ten_minutes)
def updates_on_organization(request, organization_id):
    if not organization_id:
        return empty_response()

    return JsonResponse(latest_updates(organization_id), encoder=JSEncoder)


def organization_autcomplete(
    request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER, parameter: str = ""
):
    # See Django Auto Complete for more info.
    qs = Organization.objects.all()
    qs = qs.filter(type=get_organization_type(organization_type))
    qs = qs.filter(country=get_country(country))
    qs = qs.filter(name__icontains=parameter).values_list("name", flat=True)

    return JsonResponse(list(qs), encoder=JSEncoder, safe=False)


@cache_page(ten_minutes)
def upcoming_and_past_scans(request):
    data = get_next_and_last_scans()
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@cache_page(one_day)
def screenshot(request, endpoint_id=0):
    # get the latest screenshot from an endpoint.

    screenshot = Screenshot.objects.all().filter(endpoint=endpoint_id).order_by("-created_on").first()

    if not screenshot:
        return HttpResponse()

    if not screenshot.image:
        return HttpResponse()

    wrapper = FileWrapper(screenshot.image.file.open("rb"))
    response = HttpResponse(wrapper, content_type="image/PNG")
    return response


def get_json_body(request):
    try:
        user_input = json.loads(request.body)
    except json.JSONDecodeError:
        user_input = {}

    return user_input


def _explain(request):
    if not request.user.is_authenticated:
        return JsonResponse({}, encoder=JSEncoder, safe=False)

    request = get_json_body(request)

    data = explain(
        request.get("scan_id"),
        request.get("scan_type"),
        request.get("explanation"),
        request.get("explained_by"),
        request.get("validity"),
    )

    return JsonResponse(data, encoder=JSEncoder, safe=False)


@user_passes_test(user_is_staff_member)
def _remove_explain(request):
    request = get_json_body(request)
    data = remove_explanation(request.get("scan_id"), request.get("scan_type"))
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@user_passes_test(user_is_staff_member)
def _add_urls(request):
    request = get_json_body(request)
    data = add_urls(request.get("organization_id"), request.get("urls"))
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@user_passes_test(user_is_staff_member)
def _add_proxies(request):
    request = get_json_body(request)
    data = add_proxies(request.get("proxies"))
    return JsonResponse(data, encoder=JSEncoder, safe=False)


@user_passes_test(user_is_staff_member)
def _switch_lattitude_and_longitude(request, organization_id=0):
    return JsonResponse(switch_lattitude_and_longitude(organization_id), encoder=JSEncoder, safe=False)


@user_passes_test(user_is_staff_member)
def _add_organization(request):
    request = get_json_body(request)
    return JsonResponse(add_organization(request), encoder=JSEncoder, safe=False)


def planned_scan_progress(request):
    return JsonResponse(plannedscan.get_latest_progress(), encoder=JSEncoder, safe=False)


def map_health(request, country: str = DEFAULT_COUNTRY, organization_type=DEFAULT_LAYER):
    return JsonResponse(get_lastest_map_health_data(country, organization_type), encoder=JSEncoder, safe=False)