mixins.py 6.59 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
    Base permission-aware and context-aware view structure
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

31
32
33
34
35
    def load_objects(self):
        """
        Hook to set self.* members from request parameters, so that they are
        available to the rest of the view members.
        """
36
        pass
Enrico Zini's avatar
Enrico Zini committed
37

38
    def check_permissions(self, request=None):
39
40
41
42
43
44
        """
        Raise PermissionDenied if some of the permissions requested by the view
        configuration are not met.

        Subclasses can extend this to check their own permissions.
        """
45
        if request is not None:
46
            # Hack to play well as a mixin in rest_framework's ApiView
47
48
            super().check_permissions(request)

49
50
51
        if self.require_visitor and (
                (not self.request.user.is_authenticated)
                or self.require_visitor not in self.request.user.perms):
52
            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
69
70
class VisitorTemplateView(VisitorMixin, TemplateView):
    pass

71

Enrico Zini's avatar
Enrico Zini committed
72
73
class VisitPersonMixin(VisitorMixin):
    """
Enrico Zini's avatar
Enrico Zini committed
74
    Visit a person record. Adds self.person and self.visit_perms with the
Enrico Zini's avatar
Enrico Zini committed
75
76
    permissions the visitor has over the person
    """
77
78
    # 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
79
    require_visit_perms = None
80

81
    def get_person(self):
82
83
84
        """
        Return the person visited by this view
        """
Enrico Zini's avatar
Enrico Zini committed
85
        key = self.kwargs.get("key", None)
86
        if key is not None:
87
88
            return bmodels.Person.lookup_or_404(key)

89
90
91
92
93
        # Default to the current user
        if not self.request.user.is_authenticated:
            raise PermissionDenied
        return self.request.user

94
95
    def get_person_menu_entries(self):
        res = super().get_person_menu_entries()
96
97
        res.append(NavLink(self.person.get_ddpo_url(), _("DDPO"), "tasks"))
        res.append(NavLink(self.person.get_portfolio_url(), _("Portfolio"), "newspaper-o"))
98
        url = self.person.get_contributors_url()
99
        if url:
100
101
102
103
104
            disabled = False
        else:
            disabled = True
            url = "#"
        res.append(NavLink(url, _("Contributor"), "address-card", disabled))
105
        if self.request.user.is_authenticated:
106
            res.append(NavLink(
107
                reverse("minechangelogs:search", kwargs={"key": self.person.lookup_key}), _("Changelogs"), "history"))
108
            if self.request.user.is_superuser:
109
                res.append(NavLink(
110
                    self.person.get_admin_url(), _("Admin"), "microchip"))
111
112
                if "am_candidate" in self.person.perms:
                    res.append(NavLink(
113
                        reverse("admin:backend_am_add") + f"?person={self.person.id}", _("Make AM")))
114
                if self.person.is_dd:
Enrico Zini's avatar
Enrico Zini committed
115
                    res.append(NavLink(
116
                        reverse("mia:wat_ping", kwargs={"key": self.person.lookup_key}), _("WAT ping"), "heartbeat"))
117
118
119
                    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
120
                        res.append(NavLink(emeritus_link, _("One click emeritus"), "bed"))
121
122
                if self.person.is_am:
                    res.append(NavLink(
123
                        reverse("person:amprofile", kwargs={"key": self.person.lookup_key}), _("AM Profile"), "gear"))
124
125
            if "view_person_audit_log" in self.visit_perms:
                res.append(NavLink(
126
                    reverse("person:identities", kwargs={
127
                        "key": self.person.lookup_key}), _("Logins"), "sign-in"))
128
129
        return res

130
    def get_visit_perms(self):
131
        return self.person.permissions_of(self.request.user)
Enrico Zini's avatar
Enrico Zini committed
132

133
    def load_objects(self):
134
        super().load_objects()
135
        self.person = self.get_person()
136
        self.visit_perms = self.get_visit_perms()
137
138

    def check_permissions(self):
139
        super().check_permissions()
Enrico Zini's avatar
Enrico Zini committed
140
        if self.require_visit_perms and self.require_visit_perms not in self.visit_perms:
141
142
            raise PermissionDenied

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

149

Enrico Zini's avatar
Enrico Zini committed
150
151
class VisitPersonTemplateView(VisitPersonMixin, TemplateView):
    pass
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
181
182
183
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
184
                u = bmodels.Person.objects.get(ldap_fields__uid=uid)
185
186
187
188
189
            except bmodels.Person.DoesNotExist:
                u = None
            if u is not None:
                self.request.user = u
        super().load_objects()