From e59bf4a4b7acd6d1f06179777d9011fab7eb7147 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Tue, 17 Jul 2012 20:36:21 -0400 Subject: added LDAP_AUTHENTICATE_FUNCTION and LDAP_AUTHENTICATE_FAILURE_FUNCTION --- askbot/deps/django_authopenid/ldap_auth.py | 43 ++++++++++++++++++++-------- askbot/deps/django_authopenid/views.py | 45 +++++++++++++++++++----------- askbot/doc/source/optional-modules.rst | 16 +++++++++++ 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/askbot/deps/django_authopenid/ldap_auth.py b/askbot/deps/django_authopenid/ldap_auth.py index 109bfad7..79d05b44 100644 --- a/askbot/deps/django_authopenid/ldap_auth.py +++ b/askbot/deps/django_authopenid/ldap_auth.py @@ -1,10 +1,11 @@ import logging from django.conf import settings as django_settings from django.contrib.auth.models import User -from django.forms import EmailField +from django.forms import EmailField, ValidationError from askbot.conf import settings as askbot_settings -from askbot.models.signals import user_registered from askbot.deps.django_authopenid.models import UserAssociation +from askbot.models.signals import user_registered +from askbot.utils.loading import load_module LOG = logging.getLogger(__name__) @@ -25,7 +26,7 @@ def split_name(full_name, name_format): raise ValueError('Unexpected value of name_format') -def ldap_authenticate(username, password): +def ldap_authenticate_default(username, password): """ Authenticate using ldap. LDAP parameter setup is described in @@ -38,9 +39,19 @@ def ldap_authenticate(username, password): * last_name * ldap_username * email (optional only if there is valid email) - + * success - boolean, True if authentication succeeded + python-ldap must be installed http://pypi.python.org/pypi/python-ldap/2.4.6 + + NOTE: if you are planning to implement a custom + LDAP authenticate function (python path to which can + be provided via setting `ASKBOT_LDAP_AUTHENTICATE` + setting in the settings.py file) - implement + the function just like this - accepting user name + and password and returning dict with the same values. + The returned dictionary can contain additional values + that you might find useful. """ import ldap user_information = None @@ -112,14 +123,14 @@ def ldap_authenticate(username, password): askbot_settings.LDAP_BASE_DN.encode(encoding), ldap.SCOPE_SUBTREE, user_filter.encode(encoding), - get_attrs + get_attrs ) if user_search_result: # User found in LDAP Directory user_dn = user_search_result[0][0] user_information = user_search_result[0][1] ldap_session.simple_bind_s(user_dn, password.encode(encoding)) #raises INVALID_CREDENTIALS ldap_session.unbind_s() - + if given_name_field and surname_field: last_name = user_information.get(surname_field, [''])[0] first_name = user_information.get(given_name_field, [''])[0] @@ -131,7 +142,8 @@ def ldap_authenticate(username, password): user_info = { 'first_name': first_name, 'last_name': last_name, - 'ldap_username': user_information[login_name_field][0] + 'ldap_username': user_information[login_name_field][0], + 'success': True } try: @@ -139,21 +151,22 @@ def ldap_authenticate(username, password): user_info['email'] = EmailField().clean(email) except ValidationError: pass - - return user_info else: - return None + user_info['success'] = False except ldap.INVALID_CREDENTIALS, e: return None # Will fail login on return of None + user_info['success'] = False except ldap.LDAPError, e: LOG.error("LDAPError Exception") LOG.exception(e) - return None + user_info['success'] = False except Exception, e: LOG.error("Unexpected Exception Occurred") LOG.exception(e) - return None + user_info['success'] = False + + return user_info def ldap_create_user(user_info): @@ -181,3 +194,9 @@ def ldap_create_user(user_info): assoc.provider_name = 'ldap' assoc.save() return assoc + +LDAP_AUTH_FUNC_PATH = getattr(django_settings, 'LDAP_AUTHENTICATE_FUNCTION', None) +if LDAP_AUTH_FUNC_PATH: + ldap_authenticate = load_module(LDAP_AUTH_FUNC_PATH) +else: + ldap_authenticate = ldap_authenticate_default diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py index 51f04df7..bea8ffe9 100644 --- a/askbot/deps/django_authopenid/views.py +++ b/askbot/deps/django_authopenid/views.py @@ -34,12 +34,13 @@ import datetime from django.http import HttpResponseRedirect, get_host, Http404 from django.http import HttpResponse from django.template import RequestContext, Context -from django.conf import settings +from django.conf import settings as django_settings from askbot.conf import settings as askbot_settings from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.contrib.auth import authenticate from django.core.urlresolvers import reverse +from django.forms.util import ErrorList from django.views.decorators import csrf from django.utils.encoding import smart_unicode from django.utils.html import escape @@ -50,6 +51,7 @@ from recaptcha_works.decorators import fix_recaptcha_remote_ip from askbot.skins.loaders import render_into_skin, get_template from askbot.deps.django_authopenid.ldap_auth import ldap_create_user from askbot.deps.django_authopenid.ldap_auth import ldap_authenticate +from askbot.utils.loading import load_module from urlparse import urlparse from openid.consumer.consumer import Consumer, \ @@ -136,10 +138,10 @@ def ask_openid( on_failure = on_failure or signin_failure trust_root = getattr( - settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/' + django_settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/' ) if xri.identifierScheme(openid_url) == 'XRI' and getattr( - settings, 'OPENID_DISALLOW_INAMES', False + django_settings, 'OPENID_DISALLOW_INAMES', False ): msg = _("i-names are not supported") logging.debug('openid failed because i-names are not supported') @@ -287,7 +289,7 @@ def signin(request): #1) url parameter "next" - if explicitly set #2) url from django setting LOGIN_REDIRECT_URL #3) home page of the forum - login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', None) + login_redirect_url = getattr(django_settings, 'LOGIN_REDIRECT_URL', None) next_url = get_next_url(request, default = login_redirect_url) logging.debug('next url is %s' % next_url) @@ -327,7 +329,7 @@ def signin(request): else: #try to login again via LDAP user_info = ldap_authenticate(username, password) - if user_info: + if user_info['success']: if askbot_settings.LDAP_AUTOCREATE_USERS: #create new user or user = ldap_create_user(user_info).user @@ -349,10 +351,21 @@ def signin(request): redirect_url = next_url ) else: - request.user.message_set.create( - _('Incorrect user name or password') - ) - return HttpResponseRedirect(request.path) + auth_fail_func_path = getattr( + django_settings, + 'LDAP_AUTHENTICATE_FAILURE_FUNCTION', + None + ) + + if auth_fail_func_path: + auth_fail_func = load_module(auth_fail_func_path) + auth_fail_func(user_info, login_form) + else: + errors = login_form._errors.setdefault('__all__', ErrorList()) + errors.append( + _('Login failed, were your password/login name correct?') + ) + #return HttpResponseRedirect(request.path) else: if password_action == 'login': user = authenticate( @@ -845,10 +858,10 @@ def register(request, login_provider_name=None, user_identifier=None): login_provider_name - as provider_name this function may need to be refactored to simplify the usage pattern - + template : authopenid/complete.html """ - + logging.debug('') next_url = get_next_url(request) @@ -909,7 +922,7 @@ def register(request, login_provider_name=None, user_identifier=None): user.save() user_registered.send(None, user = user) - + logging.debug('creating new openid user association for %s') UserAssociation( @@ -921,7 +934,7 @@ def register(request, login_provider_name=None, user_identifier=None): del request.session['user_identifier'] del request.session['login_provider_name'] - + logging.debug('logging the user in') user = authenticate(method = 'force', user_id = user.id) @@ -971,7 +984,7 @@ def register(request, login_provider_name=None, user_identifier=None): data = { 'openid_register_form': register_form, 'email_feeds_form': email_feeds_form, - 'default_form_action': settings.LOGIN_URL, + 'default_form_action': django_settings.LOGIN_URL, 'provider':mark_safe(provider_logo), 'username': username, 'email': email, @@ -1062,7 +1075,7 @@ def signup_with_password(request): # 'password': password, #}) #message = message_template.render(message_context) - #send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, + #send_mail(subject, message, django_settings.DEFAULT_FROM_EMAIL, # [user.email]) #logging.debug('new password acct created, confirmation email sent!') return HttpResponseRedirect(next) @@ -1175,7 +1188,7 @@ def _send_email_key(user): } template = get_template('authopenid/email_validation.txt') message = template.render(data) - send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]) + send_mail(subject, message, django_settings.DEFAULT_FROM_EMAIL, [user.email]) def send_new_email_key(user,nomessage=False): import random diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst index 5db78f70..9e77eb02 100644 --- a/askbot/doc/source/optional-modules.rst +++ b/askbot/doc/source/optional-modules.rst @@ -125,6 +125,22 @@ There are three more optional parameters that must go to the ``settings.py`` fil * ``LDAP_EXTRA_OPTIONS``, a list of two-item tuples - of names and values of the options. Option names must be upper case strings all starting with ``OPT_`` as described in the `python ldap library documentation `_. An often used option is (`OPT_REFERRALS`, 0). +* ``LDAP_AUTHENTICATE_FUNCTION`` - dotted python path to optional function that + can override the default `ldap_authenticate` function. This function allows to + completely customize the LDAP login procedure. + To see what is expected of this function (input parameters and the return value) - + look at the end of the doc string at + `askbot.deps.django_authopenid.ldap_auth.ldap_authenticate_default`. + One use case for the custom function is determining to which group + a user might belong or check any additional access rules that might be + stored in your LDAP directory. Another use case - is the case when + the default procedure just does not work for you. +* ``LDAP_AUTHENICATE_FAILURE_FUNCTION`` - python dotted path to an additional function + that may be called after a unsuccessful authentication. + This function can be used to set custom error messages to the login form. + The function should take two parameters (in the following order): user_info, login_form. + user_info - is the same dictionary + that is returned by the `ldap_authenticate` function. Use these when you have the "directory master passsword" - for a specific user who can access the rest of the directory, -- cgit v1.2.3-1-g7c22