From 135ebc93e644dc42f4c8d7fe8d556d728acd2043 Mon Sep 17 00:00:00 2001
From: Shirlei Chaves <shirlei@gmail.com>
Date: Mon, 16 Nov 2015 23:51:27 -0200
Subject: [PATCH] Add ldap auth

---
 helios_auth/auth_systems/__init__.py          |   4 +
 helios_auth/auth_systems/ldapauth.py          | 113 ++++++++++++++++++
 .../auth_systems/ldapbackend/__init__.py      |   0
 .../auth_systems/ldapbackend/backend.py       |  28 +++++
 helios_auth/media/login-icons/ldap.png        | Bin 0 -> 4758 bytes
 helios_auth/tests.py                          |  41 +++++++
 helios_auth/urls.py                           |   5 +
 requirements.txt                              |   1 +
 settings.py                                   |  30 ++++-
 9 files changed, 218 insertions(+), 4 deletions(-)
 create mode 100644 helios_auth/auth_systems/ldapauth.py
 create mode 100644 helios_auth/auth_systems/ldapbackend/__init__.py
 create mode 100644 helios_auth/auth_systems/ldapbackend/backend.py
 create mode 100644 helios_auth/media/login-icons/ldap.png

diff --git a/helios_auth/auth_systems/__init__.py b/helios_auth/auth_systems/__init__.py
index 5a0e923..aaaddb5 100644
--- a/helios_auth/auth_systems/__init__.py
+++ b/helios_auth/auth_systems/__init__.py
@@ -2,6 +2,9 @@
 AUTH_SYSTEMS = {}
 
 import twitter, password, cas, facebook, google, yahoo, linkedin, clever
+import ldapauth
+
+
 AUTH_SYSTEMS['twitter'] = twitter
 AUTH_SYSTEMS['linkedin'] = linkedin
 AUTH_SYSTEMS['password'] = password
@@ -10,6 +13,7 @@ AUTH_SYSTEMS['facebook'] = facebook
 AUTH_SYSTEMS['google'] = google
 AUTH_SYSTEMS['yahoo'] = yahoo
 AUTH_SYSTEMS['clever'] = clever
+AUTH_SYSTEMS['ldap'] = ldapauth
 
 # not ready
 #import live
diff --git a/helios_auth/auth_systems/ldapauth.py b/helios_auth/auth_systems/ldapauth.py
new file mode 100644
index 0000000..4e201f9
--- /dev/null
+++ b/helios_auth/auth_systems/ldapauth.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""
+LDAP Authentication
+Author : shirlei@gmail.com
+Version: 1.0
+Requires libldap2-dev
+django-auth-ldap 1.2.7
+LDAP authentication relies on django-auth-ldap (http://pythonhosted.org/django-auth-ldap/),
+which considers that "Authenticating against an external source is swell, but Django’s
+auth module is tightly bound to a user model. When a user logs in, we have to create a model
+object to represent them in the database."
+Helios, originally, does not rely on default django user model. Discussion about that can be
+found in:
+https://groups.google.com/forum/#!topic/helios-voting/nRHFAbAHTNA
+That considered, using a django plugin for ldap authentication, in order to not reinvent the
+wheel seems ok, since it does not alter anything on original helios user model, it is just
+for authentication purposes.
+However, two installed_apps that are added when you first create a django project, which were
+commented out in helios settings, need to be made available now:
+django.contrib.auth
+django.contrib.contenttypes'
+This will enable the native django authentication support on what django-auth-ldap is build upon.
+Further reference on
+https://docs.djangoproject.com/en/1.8/topics/auth/
+"""
+
+from django import forms
+from django.conf import settings
+from django.core.mail import send_mail
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+
+
+from helios_auth.auth_systems.ldapbackend import backend
+
+
+# some parameters to indicate that status updating is possible
+STATUS_UPDATES = False
+
+
+LOGIN_MESSAGE = "Log in with my LDAP Account"
+
+class LoginForm(forms.Form):
+    username = forms.CharField(max_length=250)
+    password = forms.CharField(widget=forms.PasswordInput(), max_length=100)
+
+
+def ldap_login_view(request):
+    from helios_auth.view_utils import render_template
+    from helios_auth.views import after
+
+    error = None
+
+    if request.method == "GET":
+            form = LoginForm()
+    else:
+            form = LoginForm(request.POST)
+
+            request.session['auth_system_name'] = 'ldap'
+
+            if request.POST.has_key('return_url'):
+                request.session['auth_return_url'] = request.POST.get('return_url')
+
+            if form.is_valid():
+                username = form.cleaned_data['username'].strip()
+                password = form.cleaned_data['password'].strip()
+
+                auth = backend.CustomLDAPBackend()
+                user = auth.authenticate(username, password)
+
+                if user:
+                    request.session['ldap_user']  = {
+                        'user_id': user.email,
+                        'name': user.first_name + ' ' + user.last_name,
+                    }
+                    return HttpResponseRedirect(reverse(after))
+                else:
+                    error = 'Bad Username or Password'
+
+    return render_template(request, 'password/login', {
+            'form': form,
+            'error': error,
+            'enabled_auth_systems': settings.AUTH_ENABLED_AUTH_SYSTEMS,
+        })
+
+
+def get_user_info_after_auth(request):
+    return {
+       'type': 'ldap',
+       'user_id' : request.session['ldap_user']['user_id'],
+       'name': request.session['ldap_user']['name'],
+       'info': {'email': request.session['ldap_user']['user_id']},
+       'token': None
+    }
+
+
+def get_auth_url(request, redirect_url = None):
+    return reverse(ldap_login_view)
+
+
+def send_message(user_id, name, user_info, subject, body):
+    send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (name, user_id)], fail_silently=False)
+
+
+def check_constraint(constraint, user_info):
+    """
+    for eligibility
+    """
+    pass
+
+def can_create_election(user_id, user_info):
+  return True
+
diff --git a/helios_auth/auth_systems/ldapbackend/__init__.py b/helios_auth/auth_systems/ldapbackend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/helios_auth/auth_systems/ldapbackend/backend.py b/helios_auth/auth_systems/ldapbackend/backend.py
new file mode 100644
index 0000000..ef1930d
--- /dev/null
+++ b/helios_auth/auth_systems/ldapbackend/backend.py
@@ -0,0 +1,28 @@
+from django.conf import settings
+
+from django_auth_ldap.backend import LDAPBackend
+from django_auth_ldap.config import LDAPSearch
+from django_auth_ldap.backend import populate_user
+
+
+class CustomLDAPBackend(LDAPBackend):
+    def authenticate(self, username, password):
+        """
+        Some ldap servers allow anonymous search but naturally return just a set
+        of user attributes. So, here we re-perform search after user is authenticated,
+        in order to populate other user attributes.
+        For now, just in cases where AUTH_LDAP_BIND_PASSWORD is empty
+        """
+        user =  super(CustomLDAPBackend, self).authenticate(username, password)
+
+        if user and settings.AUTH_LDAP_BIND_PASSWORD == '' :
+            search = self.settings.USER_SEARCH
+            if search is None:
+                raise ImproperlyConfigured('AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance.')
+            results = search.execute(user.ldap_user.connection, {'user': user.username})
+            if results is not None and len(results) == 1:
+                (user.ldap_user._user_dn, user.ldap_user.user_attrs) = results[0]
+                user.ldap_user._load_user_attrs()
+                user.ldap_user._populate_user_from_attributes()
+                user.save() 
+        return user
diff --git a/helios_auth/media/login-icons/ldap.png b/helios_auth/media/login-icons/ldap.png
new file mode 100644
index 0000000000000000000000000000000000000000..86f7807cbcf9f8d5add2c9450a1cf74b30ff1cb6
GIT binary patch
literal 4758
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
b^?ly_RmK;MOI;Wk7#KWV{an^LB{Ts5JLw`^

literal 0
HcmV?d00001

diff --git a/helios_auth/tests.py b/helios_auth/tests.py
index f07f309..9b097cc 100644
--- a/helios_auth/tests.py
+++ b/helios_auth/tests.py
@@ -13,6 +13,7 @@ from django.test import TestCase
 from django.core import mail
 
 from auth_systems import AUTH_SYSTEMS
+from helios_auth import ENABLED_AUTH_SYSTEMS
 
 class UserModelTests(unittest.TestCase):
 
@@ -128,3 +129,43 @@ class UserBlackboxTests(TestCase):
         self.assertEquals(len(mail.outbox), 1)
         self.assertEquals(mail.outbox[0].subject, "testing subject")
         self.assertEquals(mail.outbox[0].to[0], "\"Foobar User\" <foobar-test@adida.net>")
+
+
+import auth_systems.ldapauth as ldap_views
+
+
+class LDAPAuthTests(TestCase):
+    """
+    These tests relies on OnLine LDAP Test Server, provided by forum Systems:
+    http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
+    """
+
+    def setUp(self):
+        """ set up necessary django-auth-ldap settings """
+        self.password = 'password'
+        self.username = 'euclid'
+
+    def test_backend_login(self):
+        """ test if authenticates using the backend """
+        if 'ldap' in ENABLED_AUTH_SYSTEMS:
+            from helios_auth.auth_systems.ldapbackend import backend
+            auth = backend.CustomLDAPBackend()
+            user = auth.authenticate(self.username, self.password)
+            self.assertEqual(user.username, 'euclid')
+
+    def test_ldap_view_login(self):
+        """ test if authenticates using the auth system login view """
+        if 'ldap' in ENABLED_AUTH_SYSTEMS:
+            resp = self.client.post(reverse(ldap_views.ldap_login_view), {
+                'username' : self.username,
+                'password': self.password
+                }, follow=True)
+            self.assertEqual(resp.status_code, 200)
+
+    def test_logout(self):
+        """ test if logs out using the auth system logout view """
+        if 'ldap' in ENABLED_AUTH_SYSTEMS:
+            response = self.client.post(reverse(views.logout), follow=True)
+            self.assertContains(response, "not logged in")
+            self.assertNotContains(response, "euclid")
+
diff --git a/helios_auth/urls.py b/helios_auth/urls.py
index e4dca39..810d4f8 100644
--- a/helios_auth/urls.py
+++ b/helios_auth/urls.py
@@ -9,6 +9,8 @@ from django.conf.urls import *
 from views import *
 from auth_systems.password import password_login_view, password_forgotten_view
 from auth_systems.twitter import follow_view
+from auth_systems.ldapauth import ldap_login_view
+
 
 urlpatterns = patterns('',
     # basic static stuff
@@ -28,4 +30,7 @@ urlpatterns = patterns('',
 
     # twitter
     (r'^twitter/follow', follow_view),
+
+    #ldap
+    (r'^ldap/login', ldap_login_view),
 )
diff --git a/requirements.txt b/requirements.txt
index 2cc4eb8..eb774f3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,3 +22,4 @@ boto==2.27.0
 django-ses==0.6.0
 validate_email==1.2
 oauth2client==1.2
+django-auth-ldap==1.2.7
diff --git a/settings.py b/settings.py
index f1e2ab2..2f440d5 100644
--- a/settings.py
+++ b/settings.py
@@ -1,6 +1,7 @@
-
+import ldap
 import os, json
 
+from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
 # a massive hack to see if we're testing, in which case we use different settings
 import sys
 TESTING = 'test' in sys.argv
@@ -133,8 +134,8 @@ TEMPLATE_DIRS = (
 )
 
 INSTALLED_APPS = (
-#    'django.contrib.auth',
-#    'django.contrib.contenttypes',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
     'djangosecure',
     'django.contrib.sessions',
     #'django.contrib.sites',
@@ -206,7 +207,7 @@ HELIOS_VOTERS_EMAIL = True
 HELIOS_PRIVATE_DEFAULT = False
 
 # authentication systems enabled
-#AUTH_ENABLED_AUTH_SYSTEMS = ['password','facebook','twitter', 'google', 'yahoo']
+#AUTH_ENABLED_AUTH_SYSTEMS = ['password','facebook','twitter', 'google', 'yahoo','ldap']
 AUTH_ENABLED_AUTH_SYSTEMS = get_from_env('AUTH_ENABLED_AUTH_SYSTEMS', 'google').split(",")
 AUTH_DEFAULT_AUTH_SYSTEM = get_from_env('AUTH_DEFAULT_AUTH_SYSTEM', None)
 
@@ -242,6 +243,27 @@ CAS_ELIGIBILITY_REALM = get_from_env('CAS_ELIGIBILITY_REALM', "")
 CLEVER_CLIENT_ID = get_from_env('CLEVER_CLIENT_ID', "")
 CLEVER_CLIENT_SECRET = get_from_env('CLEVER_CLIENT_SECRET', "")
 
+# ldap
+# see configuration example at https://pythonhosted.org/django-auth-ldap/example.html
+AUTH_LDAP_SERVER_URI = "ldap://ldap.forumsys.com" # replace by your Ldap URI
+AUTH_LDAP_BIND_DN = "cn=read-only-admin,dc=example,dc=com"
+AUTH_LDAP_BIND_PASSWORD = "password"
+AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=example,dc=com",
+    ldap.SCOPE_SUBTREE, "(uid=%(user)s)"
+)
+# Populate the Django user from the LDAP directory.
+AUTH_LDAP_USER_ATTR_MAP = {
+    "first_name": "givenName",
+    "last_name": "sn",
+    "email": "mail",
+}
+AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
+AUTH_LDAP_FIND_GROUP_PERMS = True
+AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True
+AUTH_LDAP_CACHE_GROUPS = True
+AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
+AUTH_LDAP_ALWAYS_UPDATE_USER = False
+
 # email server
 EMAIL_HOST = get_from_env('EMAIL_HOST', 'localhost')
 EMAIL_PORT = int(get_from_env('EMAIL_PORT', "2525"))
-- 
GitLab