views.py 5.77 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
# coding: utf8
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from django.shortcuts import render
from django.views.generic import TemplateView, View
from django.views.generic.edit import FormView
from django.core import signing
from django.core.urlresolvers import reverse
from django import forms
Enrico Zini's avatar
Enrico Zini committed
12
from django.core.exceptions import PermissionDenied
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from backend.mixins import VisitorMixin
import backend.models as bmodels

def is_valid_username(username):
    if username.endswith("@users.alioth.debian.org"): return True
    if username.endswith("@debian.org"): return True
    return False


class ClaimForm(forms.Form):
    fpr = forms.CharField(label="Fingerprint", min_length=40, widget=forms.TextInput(attrs={"size": 60}))

    def clean_fpr(self):
        data = bmodels.FingerprintField.clean_fingerprint(self.cleaned_data['fpr'])
        try:
            fpr = bmodels.Fingerprint.objects.get(fpr=self.cleaned_data["fpr"])
        except bmodels.Fingerprint.DoesNotExist:
            raise forms.ValidationError("The GPG fingerprint is not known to this system. "
                                        "If you are a Debian Maintainer, and you entered the fingerprint that is in the DM keyring, "
                                        "please contact Front Desk to get this fixed.")

        if not fpr.is_active:
            raise forms.ValidationError("The GPG fingerprint corresponds to a key that is not currently the active key of the user.")

        if is_valid_username(fpr.user.username):
            raise forms.ValidationError("The GPG fingerprint corresponds to a person that has a valid Single Sign-On username.")

        return data


class Claim(VisitorMixin, FormView):
    """
    Validate and send an encrypted HMAC url to associate an alioth account with
    a DM key
    """
    template_name = "dm/claim.html"
    form_class = ClaimForm

    def pre_dispatch(self):
        super(Claim, self).pre_dispatch()
        if self.visitor is not None: raise PermissionDenied
        if self.request.sso_username is None: raise PermissionDenied
        if not is_valid_username(self.request.sso_username): raise PermissionDenied
        self.username = self.request.sso_username

    def get_context_data(self, fpr=None, **kw):
        ctx = super(Claim, self).get_context_data(**kw)
        ctx["username"] = self.username
        if fpr:
            ctx["fpr"] = fpr
            ctx["person"] = fpr.user

            key = fpr.get_key()
            if not key.key_is_fresh(): key.update_key()
            plaintext = self.request.build_absolute_uri(reverse("dm_claim_confirm", kwargs={
                "token": signing.dumps({
                    "u": self.username,
                    "f": fpr.fpr,
                })
            }))
            plaintext += "\n"
Enrico Zini's avatar
Enrico Zini committed
74
75
76
77
            # Add to context: it will not be rendered, but it can be picked up
            # by unit tests without the need to have access to the private key
            # to decode it
            ctx["plaintext"] = plaintext
78
79
80
81
82
83
84
85
86
87
88
89
90
91
            ctx["challenge"] = key.encrypt(plaintext.encode("utf8"))
        return ctx

    def form_valid(self, form):
        fpr = bmodels.Fingerprint.objects.get(fpr=form.cleaned_data["fpr"])
        return self.render_to_response(self.get_context_data(form=form, fpr=fpr))


class ClaimConfirm(VisitorMixin, TemplateView):
    """
    Validate the claim confirmation links
    """
    template_name = "dm/claim_confirm.html"

Enrico Zini's avatar
Enrico Zini committed
92
93
94
95
96
97
    def pre_dispatch(self):
        super(ClaimConfirm, self).pre_dispatch()
        if self.request.sso_username is None: raise PermissionDenied
        if not is_valid_username(self.request.sso_username): raise PermissionDenied
        self.username = self.request.sso_username

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
    def validate_token(self, token):
        parsed = signing.loads(token)
        self.errors = []

        if self.visitor is not None:
            self.errors.append("Your SSO username is already associated with a person in the system")
            return False

        # Validate fingerprint
        try:
            self.fpr = bmodels.Fingerprint.objects.get(fpr=parsed["f"])
        except bmodels.Fingerprint.DoesNotExist:
            self.fpr = None
            self.errors.append("The GPG fingerprint is not known to this system")
            return False

        if not self.fpr.is_active:
            self.errors.append("The GPG fingerprint corresponds to a key that is not currently the active key of the user.")

        if is_valid_username(self.fpr.user.username):
            self.errors.append("The GPG fingerprint corresponds to a person that has a valid Single Sign-On username.")

Enrico Zini's avatar
Enrico Zini committed
120
121
122
        if self.fpr.user.is_dd:
            self.errors.append("The GPG fingerprint corresponds to a Debian Developer.")

123
        # Validate username
Enrico Zini's avatar
Enrico Zini committed
124
125
        if self.username != parsed["u"]:
            self.errors.append("The token was not generated by you")
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

        try:
            existing_person = bmodels.Person.objects.get(username=self.username)
        except bmodels.Person.DoesNotExist:
            existing_person = None
        if existing_person is not None:
            self.errors.append("The SSO username is already associated with a different person in the system")

        return not self.errors

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

        if self.validate_token(self.kwargs["token"]):
            # Do the mapping
Enrico Zini's avatar
Enrico Zini committed
141
142
            self.fpr.user.username = self.username
            self.fpr.user.save(audit_author=self.fpr.user, audit_notes="claimed account via /dm/claim")
143
            ctx["mapped"] = True
Enrico Zini's avatar
Enrico Zini committed
144
            ctx["person"] = self.fpr.user
145
146
147
148
149
            ctx["fpr"] = self.fpr
            ctx["username"] = self.username
        ctx["errors"] = self.errors

        return ctx