commit b3843f123fbcbbd8de2e69abe6418cde2904e0fc
Author: Serghei Mihai <smihai@entrouvert.com>
Date:   Thu Apr 30 18:15:50 2015 +0200

    local accounts authentication (#7066)

diff --git a/uauth/backends.py b/uauth/backends.py
new file mode 100644
index 0000000..1084afa
--- /dev/null
+++ b/uauth/backends.py
@@ -0,0 +1,14 @@
+from .organization.models import LocalAccount
+from datetime import datetime
+
+class LocalAccountPasswordBackend(object):
+
+    def authenticate(self, login, password, organization):
+        try:
+            account = LocalAccount.objects.get(username=login, active=True,
+                                               organization=organization)
+            if account.password == password:
+                if not account.expired:
+                    return account
+        except LocalAccount.DoesNotExist:
+            return None
diff --git a/uauth/settings.py b/uauth/settings.py
index d3ffa8c..4ff8a09 100644
--- a/uauth/settings.py
+++ b/uauth/settings.py
@@ -99,6 +99,7 @@ LDAP_CONF = {
 
 AUTHENTICATION_BACKENDS = global_settings.AUTHENTICATION_BACKENDS + (
     'mellon.backends.SAMLBackend',
+    'uauth.backends.LocalAccountPasswordBackend',
 )
 
 MELLON_ATTRIBUTE_MAPPING = {
diff --git a/uauth/static/css/style.css b/uauth/static/css/style.css
index 48a304f..760ffd6 100644
--- a/uauth/static/css/style.css
+++ b/uauth/static/css/style.css
@@ -35,6 +35,10 @@ ul.login li, #guest-login ul li, #voucher-login ul li, .loginbox li {
     border-bottom: 1px solid #bbb;
 }
 
+#login label {
+    display: block;
+}
+
 .loginbox {
     width: 300px;
     margin: 0 auto;
diff --git a/uauth/utils.py b/uauth/utils.py
index 29e8fef..41f8590 100644
--- a/uauth/utils.py
+++ b/uauth/utils.py
@@ -1,6 +1,7 @@
 import os
 import logging
 import json
+from uuid import uuid4
 
 try:
     import ldap
@@ -42,7 +43,9 @@ def get_ldap_connection(conf=settings.LDAP_CONF):
         return
     return conn
 
-def create_radius_user(username, password, **kwargs):
+def create_radius_user(**kwargs):
+    username = uuid4().get_hex()
+    password = uuid4().get_hex()
     connection = get_ldap_connection()
     if connection:
         attrs = {'objectClass': ['radiusprofile', 'radiusObjectProfile'],
@@ -54,6 +57,6 @@ def create_radius_user(username, password, **kwargs):
         dn = 'uid=%s,%s' % (username, settings.LDAP_CONF['dn'])
         logger.debug('creating new radius user: %s' % dn)
         connection.add_s(dn, ldif)
-        return True
+        return username, password
     else:
         return False
diff --git a/uauth/views.py b/uauth/views.py
index b222014..e9450be 100644
--- a/uauth/views.py
+++ b/uauth/views.py
@@ -1,17 +1,19 @@
 import json
-from uuid import uuid4
 import requests
 from xml.etree import ElementTree
 
 from django.views.generic.base import TemplateView
+from django.views.generic import FormView
 from django.views.decorators.csrf import csrf_exempt
 from django.shortcuts import render_to_response
 from django.core import signing
 from django.http.request import QueryDict
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext_lazy as _
 
 from mellon.views import LoginView as MellonLoginView
 
-from .organization.models import Organization
+from .organization.models import Organization, LocalAccount
 from .forms import GuestLoginForm, VoucherLoginForm
 from .utils import create_radius_user, is_organization_idp, \
     get_idp_list
@@ -22,8 +24,29 @@ class HomeView(TemplateView):
 
 homepage = HomeView.as_view()
 
+class LoginMixin(object):
+    def login(self, organization):
+        context = {'organization': organization}
+        result = create_radius_user()
+        if result:
+            username, password = result
+            params = QueryDict(self.request.session[organization.slug], mutable=True)
+            hotspot_url = organization.hotspot_url
+
+            if 'login_url' in params:
+                hotspot_url = params.pop('login_url')[0]
+
+            context.update({'params':  params.urlencode(),
+                            'hotspot_url': hotspot_url,
+                            'data': {'username': username,
+                                     'password': password}
+                        })
+            return render_to_response('uauth/%s_login_successful.html' % organization.hotspot_type,
+                                          context)
+        return render_to_response('uauth/login_failed.html', context)
+
 
-class LoginView(MellonLoginView):
+class LoginView(LoginMixin, MellonLoginView):
 
     def authenticate(self, request, login, attributes):
         relayState = signing.loads(login.msgRelayState)
@@ -41,44 +64,38 @@ class LoginView(MellonLoginView):
             eduPersonTargetedID_NameQualifier = attributes['issuer']
 
         if is_organization_idp(eduPersonTargetedID_NameQualifier, organization):
-            username = uuid4().get_hex()
-            password = uuid4().get_hex()
-            context = {'organization': organization}
-            if create_radius_user(username, password):
-                params = QueryDict(self.request.session[organization.slug], mutable=True)
-                hotspot_url = organization.hotspot_url
-
-                if 'login_url' in params:
-                    hotspot_url = params.pop('login_url')[0]
-
-                context.update({'params':  params.urlencode(),
-                                'hotspot_url': hotspot_url,
-                                'data': {'username': username,
-                                         'password': password
-                                     }
-                            })
-                return render_to_response('uauth/%s_login_successful.html' % organization.hotspot_type,
-                                          context)
-        return render_to_response('uauth/login_failed.html', context)
+            return self.login(organization)
 
 login = csrf_exempt(LoginView.as_view())
 
 
-class OrganizationPageView(TemplateView):
+class OrganizationPageView(LoginMixin, FormView):
+    form_class = GuestLoginForm
     template_name = 'uauth/organization.html'
 
     def get_context_data(self, **kwargs):
         context = super(OrganizationPageView, self).get_context_data(**kwargs)
         idps = get_idp_list()
-        organization = Organization.objects.get(slug=kwargs['organization_slug'])
+        organization = Organization.objects.get(slug=self.kwargs['organization_slug'])
         self.request.session[organization.slug] = self.request.GET.urlencode()
         relay = signing.dumps({'organization': organization.slug})
         context.update({'idps': idps,
-                        'guest_login_form': GuestLoginForm(),
-                        'voucher_login_form': VoucherLoginForm(),
+                        'guest_login_form': kwargs['form'],
                         'relay': relay,
-                        'organization': organization
+                        'organization': organization,
+                        'voucher_login_form': VoucherLoginForm()
                         })
         return context
 
+    def form_valid(self, form):
+        data = form.cleaned_data
+        organization = Organization.objects.get(slug=self.kwargs['organization_slug'])
+        data.update({'organization': organization})
+        user = authenticate(**data)
+        if user:
+            return self.login(organization)
+        else:
+            form.add_error(None, _('Unknown or inactive user'))
+            return self.form_invalid(form)
+
 organization = OrganizationPageView.as_view()
