File: forms.py

package info (click to toggle)
django-cas-server 3.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,704 kB
  • sloc: python: 6,154; makefile: 275; xml: 100; javascript: 92; sh: 3
file content (205 lines) | stat: -rw-r--r-- 8,216 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
# more details.
#
# You should have received a copy of the GNU General Public License version 3
# along with this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# (c) 2015-2020 Valentin Samir
"""forms for the app"""
from .default_settings import settings

from django import forms
from django.forms import widgets

import cas_server.utils as utils
import cas_server.models as models

import sys
if sys.version_info < (3, ):
    from django.utils.translation import ugettext_lazy as _
else:
    from django.utils.translation import gettext_lazy as _


class BootsrapForm(forms.Form):
    """
        Bases: :class:`django.forms.Form`

        Form base class to use boostrap then rendering the form fields
    """
    def __init__(self, *args, **kwargs):
        super(BootsrapForm, self).__init__(*args, **kwargs)
        for field in self.fields.values():
            # Only tweak the field if it will be displayed
            if not isinstance(field.widget, widgets.HiddenInput):
                attrs = {}
                if (
                    isinstance(field.widget, (widgets.Input, widgets.Select, widgets.Textarea)) and
                    not isinstance(field.widget, (widgets.CheckboxInput,))
                ):
                    attrs['class'] = "form-control"
                if isinstance(field.widget, (widgets.Input, widgets.Textarea)) and field.label:
                    attrs["placeholder"] = field.label
                if field.required:
                    attrs["required"] = "required"
                field.widget.attrs.update(attrs)


class BaseLogin(BootsrapForm):
    """
        Bases: :class:`BootsrapForm`

        Base form with all field possibly hidden on the login pages
    """
    #: The service url for which the user want a ticket
    service = forms.CharField(widget=forms.HiddenInput(), required=False)
    #: A valid LoginTicket to prevent POST replay
    lt = forms.CharField(widget=forms.HiddenInput(), required=False)
    #: Is the service asking the authentication renewal ?
    renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
    #: Url to redirect to if the authentication fail (user not authenticated or bad service)
    gateway = forms.CharField(widget=forms.HiddenInput(), required=False)
    method = forms.CharField(widget=forms.HiddenInput(), required=False)


class WarnForm(BaseLogin):
    """
        Bases: :class:`BaseLogin`

        Form used on warn page before emiting a ticket
    """
    #: ``True`` if the user has been warned of the ticket emission
    warned = forms.BooleanField(widget=forms.HiddenInput(), required=False)


class FederateSelect(BaseLogin):
    """
        Bases: :class:`BaseLogin`

        Form used on the login page when ``settings.CAS_FEDERATE`` is ``True``
        allowing the user to choose an identity provider.
    """
    #: The providers the user can choose to be used as authentication backend
    provider = forms.ModelChoiceField(
        queryset=models.FederatedIendityProvider.objects.filter(display=True).order_by(
            "pos",
            "verbose_name",
            "suffix"
        ),
        to_field_name="suffix",
        label=_('Identity provider'),
    )
    #: A checkbox to ask to be warn before emiting a ticket for another service
    warn = forms.BooleanField(
        label=_('Warn me before logging me into other sites.'),
        required=False
    )
    #: A checkbox to remember the user choices of :attr:`provider<FederateSelect.provider>`
    remember = forms.BooleanField(label=_('Remember the identity provider'), required=False)


class UserCredential(BaseLogin):
    """
         Bases: :class:`BaseLogin`

         Form used on the login page to retrive user credentials
     """
    #: The user username
    username = forms.CharField(
        label=_('username'),
        widget=forms.TextInput(attrs={'autofocus': 'autofocus'})
    )
    #: The user password
    password = forms.CharField(label=_('password'), widget=forms.PasswordInput)
    #: A checkbox to ask to be warn before emiting a ticket for another service
    warn = forms.BooleanField(
        label=_('Warn me before logging me into other sites.'),
        required=False
    )

    def clean(self):
        """
            Validate that the submited :attr:`username` and :attr:`password` are valid

            :raises django.forms.ValidationError: if the :attr:`username` and :attr:`password`
                are not valid.
            :return: The cleaned POST data
            :rtype: dict
        """
        cleaned_data = super(UserCredential, self).clean()
        if "username" in cleaned_data and "password" in cleaned_data:
            auth = utils.import_attr(settings.CAS_AUTH_CLASS)(cleaned_data["username"])
            if auth.test_password(cleaned_data["password"]):
                cleaned_data["username"] = auth.username
            else:
                raise forms.ValidationError(
                    _(u"The credentials you provided cannot be determined to be authentic.")
                )
        return cleaned_data


class FederateUserCredential(UserCredential):
    """
        Bases: :class:`UserCredential`

        Form used on a auto submited page for linking the views
        :class:`FederateAuth<cas_server.views.FederateAuth>` and
        :class:`LoginView<cas_server.views.LoginView>`.

        On successful authentication on a provider, in the view
        :class:`FederateAuth<cas_server.views.FederateAuth>` a
        :class:`FederatedUser<cas_server.models.FederatedUser>` is created by
        :meth:`cas_server.federate.CASFederateValidateUser.verify_ticket` and the user is redirected
        to :class:`LoginView<cas_server.views.LoginView>`. This form is then automatically filled
        with infos matching the created :class:`FederatedUser<cas_server.models.FederatedUser>`
        using the ``ticket`` as one time password and submited using javascript. If javascript is
        not enabled, a connect button is displayed.

        This stub authentication form, allow to implement the federated mode with very few
        modificatons to the :class:`LoginView<cas_server.views.LoginView>` view.
    """

    def __init__(self, *args, **kwargs):
        super(FederateUserCredential, self).__init__(*args, **kwargs)
        # All fields are hidden and auto filled by the /login view logic
        for name, field in self.fields.items():
            field.widget = forms.HiddenInput()
            self[name].display = False

    def clean(self):
        """
            Validate that the submited :attr:`username` and :attr:`password` are valid using
            the :class:`CASFederateAuth<cas_server.auth.CASFederateAuth>` auth class.

            :raises django.forms.ValidationError: if the :attr:`username` and :attr:`password`
                do not correspond to a :class:`FederatedUser<cas_server.models.FederatedUser>`.
            :return: The cleaned POST data
            :rtype: dict
        """
        cleaned_data = super(FederateUserCredential, self).clean()
        try:
            user = models.FederatedUser.get_from_federated_username(cleaned_data["username"])
            user.ticket = ""
            user.save()
        # should not happed as if the FederatedUser do not exists, super should
        # raise before a ValidationError("bad user")
        except models.FederatedUser.DoesNotExist:  # pragma: no cover (should not happend)
            raise forms.ValidationError(
                _(u"User not found in the temporary database, please try to reconnect")
            )
        return cleaned_data


class TicketForm(forms.ModelForm):
    """
        Bases: :class:`django.forms.ModelForm`

        Form for Tickets in the admin interface
    """
    class Meta:
        model = models.Ticket
        exclude = []
    service = forms.CharField(label=_('service'), widget=forms.TextInput)