mixins.py 6.54 KB
Newer Older
1
2
from __future__ import annotations
from django.utils.translation import ugettext as _
3
from django.views.generic import TemplateView
4
from django.core.exceptions import PermissionDenied
5
from django.core import signing
6
from django.urls import reverse
7
from . import models as bmodels
8
from nmlayout.mixins import NM2LayoutMixin, NavLink
9

Enrico Zini's avatar
Enrico Zini committed
10

11
12
13
14
15
16
17
18
19
20
21
22
class OverrideView(Exception):
    """
    Allow to override the current view using a different view function in
    load_objects, check_permissions and pre_dispatch.

    This can be used to show nice error messages when special cases are
    detected.
    """
    def __init__(self, method):
        self.method = method


23
class VisitorMixin(NM2LayoutMixin):
24
    """
25
    Add self.visitor to the View for the person visiting the site
26
    """
Enrico Zini's avatar
Enrico Zini committed
27
    # Define to "dd" "am" or "admin" to raise PermissionDenied if the
28
29
30
    # given test on the visitor fails
    require_visitor = None

Enrico Zini's avatar
Enrico Zini committed
31
    def set_visitor_info(self):
Enrico Zini's avatar
Enrico Zini committed
32
        if not self.request.user.is_authenticated:
33
34
            self.visitor = None
        else:
Enrico Zini's avatar
Enrico Zini committed
35
            self.visitor = self.request.user
36

37
38
39
40
41
    def load_objects(self):
        """
        Hook to set self.* members from request parameters, so that they are
        available to the rest of the view members.
        """
Enrico Zini's avatar
Enrico Zini committed
42
43
        self.set_visitor_info()

44
45
46
47
48
49
50
    def check_permissions(self):
        """
        Raise PermissionDenied if some of the permissions requested by the view
        configuration are not met.

        Subclasses can extend this to check their own permissions.
        """
51
52
        if self.require_visitor and (self.visitor is None or self.require_visitor not in self.visitor.perms):
            raise PermissionDenied
53

54
55
56
    def pre_dispatch(self):
        pass

Enrico Zini's avatar
Enrico Zini committed
57
    def dispatch(self, request, *args, **kwargs):
58
59
60
61
62
        try:
            self.load_objects()
            self.check_permissions()
            self.pre_dispatch()
        except OverrideView as e:
Enrico Zini's avatar
Enrico Zini committed
63
            return e.method(request, *args, **kwargs)
64

65
        return super(VisitorMixin, self).dispatch(request, *args, **kwargs)
66
67
68

    def get_context_data(self, **kw):
        ctx = super(VisitorMixin, self).get_context_data(**kw)
69
        ctx["visitor"] = self.visitor
70
71
        return ctx

72

73
74
75
class VisitorTemplateView(VisitorMixin, TemplateView):
    pass

76

Enrico Zini's avatar
Enrico Zini committed
77
78
class VisitPersonMixin(VisitorMixin):
    """
Enrico Zini's avatar
Enrico Zini committed
79
    Visit a person record. Adds self.person and self.visit_perms with the
Enrico Zini's avatar
Enrico Zini committed
80
81
    permissions the visitor has over the person
    """
82
83
    # Define to "edit_bio" "edit_ldap" or "view_person_audit_log" to raise
    # PermissionDenied if the given test on the person-visitor fails
Enrico Zini's avatar
Enrico Zini committed
84
    require_visit_perms = None
85

86
    def get_person(self):
Enrico Zini's avatar
Enrico Zini committed
87
88
        key = self.kwargs.get("key", None)
        if key is None:
Enrico Zini's avatar
Enrico Zini committed
89
90
            if self.visitor is None:
                raise PermissionDenied
91
            return self.visitor
Enrico Zini's avatar
Enrico Zini committed
92
        else:
93
94
            return bmodels.Person.lookup_or_404(key)

95
96
    def get_person_menu_entries(self):
        res = super().get_person_menu_entries()
97
98
        res.append(NavLink(self.person.get_ddpo_url(), _("DDPO"), "tasks"))
        res.append(NavLink(self.person.get_portfolio_url(), _("Portfolio"), "newspaper-o"))
99
100
        url = self.person.get_contributors_url
        if url:
101
            res.append(NavLink(self.person.get_contributors_url(), _("Contributor"), "address-card"))
102
103
        if self.visitor is not None:
            res.append(NavLink(
104
                reverse("minechangelogs_search", kwargs={"key": self.person.lookup_key}), _("Changelogs"), "history"))
105
106
            if self.visitor.is_admin:
                res.append(NavLink(
107
                    self.person.get_admin_url(), _("Admin"), "microchip"))
108
109
                if "am_candidate" in self.person.perms:
                    res.append(NavLink(
110
                        reverse("admin:backend_am_add") + f"?person={self.person.id}", _("Make AM")))
111
                if self.person.is_dd:
Enrico Zini's avatar
Enrico Zini committed
112
113
                    res.append(NavLink(
                        reverse("mia_wat_ping", kwargs={"key": self.person.lookup_key}), _("WAT ping"), "heartbeat"))
114
115
116
                    from process.views import Emeritus
                    emeritus_link = Emeritus.get_nonauth_url(self.person, self.request)
                    if emeritus_link:
Enrico Zini's avatar
Enrico Zini committed
117
                        res.append(NavLink(emeritus_link, _("One click emeritus"), "bed"))
118
119
                if self.person.is_am:
                    res.append(NavLink(
120
                        reverse("person_amprofile", kwargs={"key": self.person.lookup_key}), _("AM Profile"), "gear"))
121
122
123
124
            if "view_person_audit_log" in self.visit_perms:
                res.append(NavLink(
                    reverse("signon_identities_person", kwargs={
                        "key": self.person.lookup_key}), _("Logins"), "sign-in"))
125
126
        return res

127
    def get_visit_perms(self):
Enrico Zini's avatar
Enrico Zini committed
128
129
        return self.person.permissions_of(self.visitor)

130
    def load_objects(self):
131
        super().load_objects()
132
        self.person = self.get_person()
133
        self.visit_perms = self.get_visit_perms()
134
135

    def check_permissions(self):
136
        super().check_permissions()
Enrico Zini's avatar
Enrico Zini committed
137
        if self.require_visit_perms and self.require_visit_perms not in self.visit_perms:
138
139
            raise PermissionDenied

Enrico Zini's avatar
Enrico Zini committed
140
    def get_context_data(self, **kw):
141
        ctx = super().get_context_data(**kw)
Enrico Zini's avatar
Enrico Zini committed
142
        ctx["person"] = self.person
143
        ctx["visit_perms"] = self.visit_perms
Enrico Zini's avatar
Enrico Zini committed
144
145
        return ctx

146

Enrico Zini's avatar
Enrico Zini committed
147
148
class VisitPersonTemplateView(VisitPersonMixin, TemplateView):
    pass
149
150


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class TokenAuthMixin:
    # Domain used for this token
    token_domain = None
    # Max age in seconds
    token_max_age = 15 * 3600 * 24

    @classmethod
    def make_token(cls, uid, **kw):
        from django.utils.http import urlencode
        kw.update(u=uid, d=cls.token_domain)
        return urlencode({"t": signing.dumps(kw)})

    def verify_token(self, decoded):
        # Extend to verify extra components of the token
        pass

    def load_objects(self):
        token = self.request.GET.get("t")
        if token is not None:
            try:
                decoded = signing.loads(token, max_age=self.token_max_age)
            except signing.BadSignature:
                raise PermissionDenied
            uid = decoded.get("u")
            if uid is None:
                raise PermissionDenied
            if decoded.get("d") != self.token_domain:
                raise PermissionDenied
            self.verify_token(decoded)
            try:
Enrico Zini's avatar
Enrico Zini committed
181
                u = bmodels.Person.objects.get(ldap_fields__uid=uid)
182
183
184
185
186
            except bmodels.Person.DoesNotExist:
                u = None
            if u is not None:
                self.request.user = u
        super().load_objects()