forms.py 7.76 KB
Newer Older
Valentin Samir's avatar
Valentin Samir committed
1 2 3 4 5 6 7 8 9
# 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.
#
Valentin Samir's avatar
Valentin Samir committed
10
# (c) 2015-2016 Valentin Samir
Valentin Samir's avatar
Valentin Samir committed
11
"""forms for the app"""
12
from .default_settings import settings
Valentin Samir's avatar
Valentin Samir committed
13

Valentin Samir's avatar
Valentin Samir committed
14
from django import forms
Valentin Samir's avatar
Valentin Samir committed
15
from django.utils.translation import ugettext_lazy as _
Valentin Samir's avatar
Valentin Samir committed
16

Valentin Samir's avatar
Valentin Samir committed
17 18
import cas_server.utils as utils
import cas_server.models as models
Valentin Samir's avatar
Valentin Samir committed
19

Valentin Samir's avatar
style  
Valentin Samir committed
20

21
class BootsrapForm(forms.Form):
Valentin Samir's avatar
Valentin Samir committed
22 23 24 25 26
    """
        Bases: :class:`django.forms.Form`

        Form base class to use boostrap then rendering the form fields
    """
27 28 29 30 31 32 33 34 35 36 37 38
    def __init__(self, *args, **kwargs):
        super(BootsrapForm, self).__init__(*args, **kwargs)
        for (name, field) in self.fields.items():
            # Only tweak the fiel if it will be displayed
            if not isinstance(field.widget, forms.HiddenInput):
                # tell to display the field (used in form.html)
                self[name].display = True
                attrs = {}
                if isinstance(field.widget, forms.CheckboxInput):
                    self[name].checkbox = True
                else:
                    attrs['class'] = "form-control"
39
                    if field.label:  # pragma: no branch (currently all field are hidden or labeled)
40 41 42 43 44 45
                        attrs["placeholder"] = field.label
                if field.required:
                    attrs["required"] = "required"
                field.widget.attrs.update(attrs)


Valentin Samir's avatar
Valentin Samir committed
46
class BaseLogin(BootsrapForm):
47
    """
Valentin Samir's avatar
Valentin Samir committed
48
        Bases: :class:`BootsrapForm`
49

Valentin Samir's avatar
Valentin Samir committed
50
        Base form with all field possibly hidden on the login pages
51 52
    """
    #: The service url for which the user want a ticket
53
    service = forms.CharField(widget=forms.HiddenInput(), required=False)
Valentin Samir's avatar
Valentin Samir committed
54 55
    #: A valid LoginTicket to prevent POST replay
    lt = forms.CharField(widget=forms.HiddenInput(), required=False)
56
    #: Is the service asking the authentication renewal ?
57
    renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
58
    #: Url to redirect to if the authentication fail (user not authenticated or bad service)
59 60
    gateway = forms.CharField(widget=forms.HiddenInput(), required=False)
    method = forms.CharField(widget=forms.HiddenInput(), required=False)
Valentin Samir's avatar
Valentin Samir committed
61 62 63 64 65 66 67 68


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

        Form used on warn page before emiting a ticket
    """
69
    #: ``True`` if the user has been warned of the ticket emission
70
    warned = forms.BooleanField(widget=forms.HiddenInput(), required=False)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
71

Valentin Samir's avatar
style  
Valentin Samir committed
72

Valentin Samir's avatar
Valentin Samir committed
73
class FederateSelect(BaseLogin):
Valentin Samir's avatar
Valentin Samir committed
74
    """
Valentin Samir's avatar
Valentin Samir committed
75
        Bases: :class:`BaseLogin`
76 77 78

        Form used on the login page when ``settings.CAS_FEDERATE`` is ``True``
        allowing the user to choose an identity provider.
Valentin Samir's avatar
Valentin Samir committed
79
    """
80
    #: The providers the user can choose to be used as authentication backend
81
    provider = forms.ModelChoiceField(
82
        queryset=models.FederatedIendityProvider.objects.filter(display=True).order_by(
83 84 85 86 87
            "pos",
            "verbose_name",
            "suffix"
        ),
        to_field_name="suffix",
Valentin Samir's avatar
Valentin Samir committed
88 89
        label=_('Identity provider'),
    )
90
    #: A checkbox to ask to be warn before emiting a ticket for another service
91 92 93 94
    warn = forms.BooleanField(
        label=_('Warn me before logging me into other sites.'),
        required=False
    )
Valentin Samir's avatar
Valentin Samir committed
95 96
    #: A checkbox to remember the user choices of :attr:`provider<FederateSelect.provider>`
    remember = forms.BooleanField(label=_('Remember the identity provider'), required=False)
Valentin Samir's avatar
Valentin Samir committed
97 98


Valentin Samir's avatar
Valentin Samir committed
99
class UserCredential(BaseLogin):
100
    """
Valentin Samir's avatar
Valentin Samir committed
101
         Bases: :class:`BaseLogin`
102 103 104 105

         Form used on the login page to retrive user credentials
     """
    #: The user username
106
    username = forms.CharField(label=_('username'))
107
    #: The user password
Valentin Samir's avatar
Valentin Samir committed
108
    password = forms.CharField(label=_('password'), widget=forms.PasswordInput)
109
    #: A checkbox to ask to be warn before emiting a ticket for another service
110 111 112 113
    warn = forms.BooleanField(
        label=_('Warn me before logging me into other sites.'),
        required=False
    )
Valentin Samir's avatar
Valentin Samir committed
114 115

    def clean(self):
116 117 118 119 120 121 122 123
        """
            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
        """
Valentin Samir's avatar
Valentin Samir committed
124
        cleaned_data = super(UserCredential, self).clean()
125
        auth = utils.import_attr(settings.CAS_AUTH_CLASS)(cleaned_data.get("username"))
Valentin Samir's avatar
Valentin Samir committed
126
        if auth.test_password(cleaned_data.get("password")):
Valentin Samir's avatar
Valentin Samir committed
127
            cleaned_data["username"] = auth.username
Valentin Samir's avatar
Valentin Samir committed
128
        else:
129 130 131
            raise forms.ValidationError(
                _(u"The credentials you provided cannot be determined to be authentic.")
            )
Valentin Samir's avatar
Valentin Samir committed
132 133 134 135
        return cleaned_data


class FederateUserCredential(UserCredential):
136
    """
137
        Bases: :class:`UserCredential`
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

        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.
    """
Valentin Samir's avatar
Valentin Samir committed
155 156 157 158 159 160 161

    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
Valentin Samir's avatar
Valentin Samir committed
162 163

    def clean(self):
164 165 166 167 168 169 170 171 172
        """
            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
        """
Valentin Samir's avatar
Valentin Samir committed
173 174
        cleaned_data = super(FederateUserCredential, self).clean()
        try:
175
            user = models.FederatedUser.get_from_federated_username(cleaned_data["username"])
Valentin Samir's avatar
Valentin Samir committed
176 177
            user.ticket = ""
            user.save()
178
        # should not happed as if the FederatedUser do not exists, super should
179 180 181 182 183
        # 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")
            )
Valentin Samir's avatar
Valentin Samir committed
184
        return cleaned_data
Valentin Samir's avatar
Valentin Samir committed
185 186 187


class TicketForm(forms.ModelForm):
188 189 190 191 192
    """
        Bases: :class:`django.forms.ModelForm`

        Form for Tickets in the admin interface
    """
Valentin Samir's avatar
Valentin Samir committed
193 194 195
    class Meta:
        model = models.Ticket
        exclude = []
196
    service = forms.CharField(label=_('service'), widget=forms.TextInput)