views.py 5.02 KB
Newer Older
1
from __future__ import annotations
Enrico Zini's avatar
Enrico Zini committed
2
from django import http
3
from django.core.exceptions import ImproperlyConfigured
4
from django.conf import settings
5
from django.core.exceptions import PermissionDenied
Enrico Zini's avatar
Enrico Zini committed
6
from django.views.generic import View
7
from django.contrib.sites.shortcuts import get_current_site
8
import backend.models as bmodels
Enrico Zini's avatar
Enrico Zini committed
9
import backend.const as const
Enrico Zini's avatar
Enrico Zini committed
10
from backend.mixins import VisitorMixin, VisitorTemplateView
Enrico Zini's avatar
Enrico Zini committed
11
import signon.models as smodels
Enrico Zini's avatar
Enrico Zini committed
12
import json
13
import datetime
14
import os
Enrico Zini's avatar
Enrico Zini committed
15
import time
16
17
18
import logging

log = logging.getLogger(__name__)
19

Enrico Zini's avatar
Enrico Zini committed
20

21
class DBExport(VisitorMixin, View):
Enrico Zini's avatar
Enrico Zini committed
22
    require_visitor = "dd"
Enrico Zini's avatar
Enrico Zini committed
23

Enrico Zini's avatar
Enrico Zini committed
24
    def get(self, request, *args, **kw):
25
26
        from backend.export import export_db

27
        exported = export_db(full=False)
Enrico Zini's avatar
Enrico Zini committed
28

29
30
31
32
33
34
        class Serializer(json.JSONEncoder):
            def default(self, o):
                if hasattr(o, "strftime"):
                    return o.strftime("%Y-%m-%d %H:%M:%S")
                return json.JSONEncoder.default(self, o)

Enrico Zini's avatar
Enrico Zini committed
35
        res = http.HttpResponse(content_type="application/json")
36
        res["Content-Disposition"] = "attachment; filename=nm-mock.json"
37
        json.dump(exported, res, cls=Serializer, indent=1)
38
        return res
39
40


Enrico Zini's avatar
Enrico Zini committed
41
42
class SalsaExport(VisitorMixin, View):
    def get(self, request, *args, **kw):
43
        site = get_current_site(request)
44
45
        salsa_host = settings.SALSA_HOST

Enrico Zini's avatar
Enrico Zini committed
46
        if not self.visitor or not self.visitor.is_staff:
47
48
49
            for addr in settings.SALSA_EXPORT_ALLOW_IPS:
                log.info("Trying to match %s against %s", addr, request.META["REMOTE_ADDR"])
                if request.META["REMOTE_ADDR"] == addr:
50
51
52
53
                    break
            else:
                log.warning("Remote address %s does not match %s", request.META["REMOTE_ADDR"], salsa_host)
                raise PermissionDenied
54

55
56
57
58
59
60
61
62
63
64
65
        signed = request.GET.get("signed") is not None
        if signed:
            import jwcrypto.jws
            import jwcrypto.jwk
            from jwcrypto.common import json_encode

            key_json = getattr(settings, "SIGNON_KEY", None)
            if key_json is None:
                raise ImproperlyConfigured("Using salsa export but SIGNON_KEY not set")
            key = jwcrypto.jwk.JWK.from_json(key_json)

Enrico Zini's avatar
Enrico Zini committed
66
        persons = []
Enrico Zini's avatar
Enrico Zini committed
67
        for identity in (
Enrico Zini's avatar
Enrico Zini committed
68
                smodels.Identity.objects.filter(issuer="salsa")
Enrico Zini's avatar
Enrico Zini committed
69
70
                                        .select_related("person", "person__ldap_fields")):
            person = identity.person
71
72
73
            if person is None:
                continue
            elif person.is_dd:
Enrico Zini's avatar
Enrico Zini committed
74
75
76
77
78
                status = "debian_developer"
            elif person.status in (const.STATUS_DM, const.STATUS_DM_GA):
                status = "debian_maintainer"
            elif person.status in (const.STATUS_EMERITUS_DD,):
                status = "debian_emeritus"
Enrico Zini's avatar
Enrico Zini committed
79
80
            elif person.status in (const.STATUS_REMOVED_DD,):
                status = "debian_removed_dd"
Enrico Zini's avatar
Enrico Zini committed
81
            else:
82
                continue
Enrico Zini's avatar
Enrico Zini committed
83

Enrico Zini's avatar
Enrico Zini committed
84
            person_data = {
85
                "aud": salsa_host,
Enrico Zini's avatar
Enrico Zini committed
86
                "sub": identity.subject,
Enrico Zini's avatar
Enrico Zini committed
87
                "https://nm.debian.org/claims/debian_status": status,
88
                "profile": request.build_absolute_uri(person.get_absolute_url()),
Enrico Zini's avatar
Enrico Zini committed
89
90
91
92
93
94
            }
            if person.is_dd:
                person_data["email"] = f"{person.ldap_fields.uid}@debian.org"
            persons.append(person_data)

        exported = {
95
            "iss": f"https://{site.domain}",
Enrico Zini's avatar
Enrico Zini committed
96
97
98
            "exp": int(time.time()) + 3600 * 24 * 3,
            "https://nm.debian.org/claims/persons": persons,
        }
Enrico Zini's avatar
Enrico Zini committed
99

100
101
102
103
104
105
106
107
108
109
110
111
        if signed:
            token = jwcrypto.jws.JWS(json_encode(exported))
            token.add_signature(
                    key, None,
                    {"alg": "RS256"},
                    {"kid": key.thumbprint()})

            return http.HttpResponse(token.serialize(), content_type="application/json")
        else:
            res = http.HttpResponse(content_type="application/json")
            json.dump(exported, res, indent=1)
            return res
Enrico Zini's avatar
Enrico Zini committed
112
113


114
115
116
117
118
119
120
121
122
123
124
125
126
class MailboxStats(VisitorTemplateView):
    template_name = "restricted/mailbox-stats.html"
    require_visitor = "admin"

    def get_context_data(self, **kw):
        ctx = super(MailboxStats, self).get_context_data(**kw)

        try:
            with open(os.path.join(settings.DATA_DIR, 'mbox_stats.json'), "rt") as infd:
                stats = json.load(infd)
        except OSError:
            stats = {}

Enrico Zini's avatar
Enrico Zini committed
127
        for email, st in list(stats["emails"].items()):
128
129
130
            st["person"] = bmodels.Person.lookup_by_email(email)
            st["date_first_py"] = datetime.datetime.fromtimestamp(st["date_first"])
            st["date_last_py"] = datetime.datetime.fromtimestamp(st["date_last"])
Enrico Zini's avatar
Enrico Zini committed
131
            if "median" not in st or st["median"] is None:
132
133
134
135
136
137
138
139
140
                st["median_py"] = None
            else:
                st["median_py"] = datetime.timedelta(seconds=st["median"])
                st["median_hours"] = st["median_py"].seconds // 3600

        ctx.update(
            emails=sorted(stats["emails"].items()),
        )
        return ctx