summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-07-12 22:02:16 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-07-12 22:02:16 -0400
commit1b94c15c0b4c8ffc98a5765aec1309092a1efd1e (patch)
treea5ecb25449b5eb63321b504c44ef2415e773d673
parent5abaedc9eb3408cee1eb42db28bdbd71570626c6 (diff)
downloadaskbot-1b94c15c0b4c8ffc98a5765aec1309092a1efd1e.tar.gz
askbot-1b94c15c0b4c8ffc98a5765aec1309092a1efd1e.tar.bz2
askbot-1b94c15c0b4c8ffc98a5765aec1309092a1efd1e.zip
rehashed the ldap registration flow once again
-rw-r--r--askbot/conf/ldap.py14
-rw-r--r--askbot/deps/django_authopenid/backends.py197
-rw-r--r--askbot/deps/django_authopenid/ldap_auth.py177
-rw-r--r--askbot/deps/django_authopenid/views.py70
4 files changed, 269 insertions, 189 deletions
diff --git a/askbot/conf/ldap.py b/askbot/conf/ldap.py
index 30fbcdec..5c37adb1 100644
--- a/askbot/conf/ldap.py
+++ b/askbot/conf/ldap.py
@@ -19,6 +19,20 @@ settings.register(
)
)
+settings.register(
+ livesettings.BooleanValue(
+ LDAP_SETTINGS,
+ 'LDAP_AUTOCREATE_USERS',
+ description = _('Automatically create user accounts when possible'),
+ default = False,
+ help_text = _(
+ 'Potentially reduces number of steps in the registration process '
+ 'but can expose personal information, e.g. when LDAP login name is '
+ 'the same as email address or real name.'
+ )
+ )
+)
+
LDAP_PROTOCOL_VERSION_CHOICES = (
('3', _('Version 3')),
('2', _('Version 2 (insecure and deprecated)!!!'))
diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py
index f1bdfa4b..7d6e126c 100644
--- a/askbot/deps/django_authopenid/backends.py
+++ b/askbot/deps/django_authopenid/backends.py
@@ -10,170 +10,12 @@ from django.conf import settings as django_settings
from django.utils.translation import ugettext as _
from askbot.deps.django_authopenid.models import UserAssociation
from askbot.deps.django_authopenid import util
+from askbot.deps.django_authopenid.ldap_auth import ldap_authenticate
+from askbot.deps.django_authopenid.ldap_auth import ldap_create_user
from askbot.conf import settings as askbot_settings
from askbot.models.signals import user_registered
-log = logging.getLogger('configuration')
-
-def split_name(full_name, name_format):
- bits = full_name.strip().split()
- if len(bits) == 1:
- bits.push('')
- elif len(bits) == 0:
- bits = ['', '']
-
- if name_format == 'first,last':
- return bits[0], bits[1]
- elif name_format == 'last,first':
- return bits[1], bits[0]
- else:
- raise ValueError('Unexpected value of name_format')
-
-
-def ldap_authenticate(username, password):
- """
- Authenticate using ldap
-
- python-ldap must be installed
- http://pypi.python.org/pypi/python-ldap/2.4.6
- """
- import ldap
- user_information = None
- try:
- ldap_session = ldap.initialize(askbot_settings.LDAP_URL)
-
- #set protocol version
- if askbot_settings.LDAP_PROTOCOL_VERSION == '2':
- ldap_session.protocol_version = ldap.VERSION2
- elif askbot_settings.LDAP_PROTOCOL_VERSION == '3':
- ldap_session.protocol_version = ldap.VERSION3
- else:
- raise NotImplementedError('unsupported version of ldap protocol')
-
- ldap.set_option(ldap.OPT_REFERRALS, 0)
-
- #set extra ldap options, if given
- if hasattr(django_settings, 'LDAP_EXTRA_OPTIONS'):
- options = django_settings.LDAP_EXTRA_OPTIONS
- for key, value in options:
- if key.startswith('OPT_'):
- ldap_key = getattr(ldap, key)
- ldap.set_option(ldap_key, value)
- else:
- raise ValueError('Invalid LDAP option %s' % key)
-
- #add optional "master" LDAP authentication, if required
- master_username = getattr(django_settings, 'LDAP_LOGIN_DN', None)
- master_password = getattr(django_settings, 'LDAP_PASSWORD', None)
-
- login_name_field = askbot_settings.LDAP_LOGIN_NAME_FIELD
- base_dn = askbot_settings.LDAP_BASE_DN
- login_template = login_name_field + '=%s,' + base_dn
- encoding = askbot_settings.LDAP_ENCODING
-
- if master_username and master_password:
- ldap_session.simple_bind_s(
- master_username.encode(encoding),
- master_password.encode(encoding)
- )
-
- user_filter = askbot_settings.LDAP_USER_FILTER_TEMPLATE % (
- askbot_settings.LDAP_LOGIN_NAME_FIELD,
- username
- )
-
- email_field = askbot_settings.LDAP_EMAIL_FIELD
-
- get_attrs = [
- email_field.encode(encoding),
- login_name_field.encode(encoding)
- #str(askbot_settings.LDAP_USERID_FIELD)
- #todo: here we have a chance to get more data from LDAP
- #maybe a point for some plugin
- ]
-
- common_name_field = askbot_settings.LDAP_COMMON_NAME_FIELD.strip()
- given_name_field = askbot_settings.LDAP_GIVEN_NAME_FIELD.strip()
- surname_field = askbot_settings.LDAP_SURNAME_FIELD.strip()
-
- if given_name_field and surname_field:
- get_attrs.append(given_name_field.encode(encoding))
- get_attrs.append(surname_field.encode(encoding))
- elif common_name_field:
- get_attrs.append(common_name_field.encode(encoding))
-
- # search ldap directory for user
- user_search_result = ldap_session.search_s(
- askbot_settings.LDAP_BASE_DN.encode(encoding),
- ldap.SCOPE_SUBTREE,
- user_filter.encode(encoding),
- 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()
-
- exact_username = user_information[login_name_field][0]
- email = user_information.get(email_field, [''])[0]
-
- if given_name_field and surname_field:
- last_name = user_information.get(surname_field, [''])[0]
- first_name = user_information.get(given_name_field, [''])[0]
- elif surname_field:
- common_name_format = askbot_settings.LDAP_COMMON_NAME_FIELD_FORMAT
- common_name = user_information.get(common_name_field, [''])[0]
- first_name, last_name = split_name(common_name, common_name_format)
-
- #here we have an opportunity to copy password in the auth_user table
- #but we don't do it for security reasons
- try:
- user = User.objects.get(username__exact=exact_username)
- # always update user profile to synchronize with ldap server
- user.set_unusable_password()
- #user.first_name = first_name
- #user.last_name = last_name
- user.email = email
- user.save()
- except User.DoesNotExist:
- # create new user in local db
- user = User()
- user.username = exact_username
- user.set_unusable_password()
- #user.first_name = first_name
- #user.last_name = last_name
- user.email = email
- user.is_staff = False
- user.is_superuser = False
- user.is_active = True
- user.save()
- user_registered.send(None, user = user)
-
- log.info('Created New User : [{0}]'.format(exact_username))
- return user
- else:
- # Maybe a user created internally (django admin user)
- try:
- user = User.objects.get(username__exact=username)
- if user.check_password(password):
- return user
- else:
- return None
- except User.DoesNotExist:
- return None
-
- except ldap.INVALID_CREDENTIALS, e:
- return None # Will fail login on return of None
- except ldap.LDAPError, e:
- log.error("LDAPError Exception")
- log.exception(e)
- return None
- except Exception, e:
- log.error("Unexpected Exception Occurred")
- log.exception(e)
- return None
-
+LOG = logging.getLogger(__name__)
class AuthBackend(object):
"""Authenticator's authentication backend class
@@ -183,6 +25,8 @@ class AuthBackend(object):
the reason there is only one class - for simplicity of
adding this application to a django project - users only need
to extend the AUTHENTICATION_BACKENDS with a single line
+
+ todo: it is not good to have one giant do all 'authenticate' function
"""
def authenticate(
@@ -222,7 +66,7 @@ class AuthBackend(object):
except User.DoesNotExist:
return None
except User.MultipleObjectsReturned:
- logging.critical(
+ LOG.critical(
('have more than one user with email %s ' +
'he/she will not be able to authenticate with ' +
'the email address in the place of user name') % email_address
@@ -323,7 +167,34 @@ class AuthBackend(object):
return None
elif method == 'ldap':
- user = ldap_authenticate(username, password)
+ user_info = ldap_authenticate(username, password)
+ if user_info is None:
+ # Maybe a user created internally (django admin user)
+ try:
+ user = User.objects.get(username__exact=username)
+ if user.check_password(password):
+ return user
+ else:
+ return None
+ except User.DoesNotExist:
+ return None
+ else:
+ #load user by association or maybe auto-create one
+ ldap_username = user_info['ldap_username']
+ try:
+ #todo: provider_name is hardcoded - possible conflict
+ assoc = UserAssociation.objects.get(
+ openid_url = ldap_username + '@ldap',
+ provider_name = 'ldap'
+ )
+ user = assoc.user
+ except UserAssociation.DoesNotExist:
+ #email address is required
+ if 'email' in user_info and askbot_settings.LDAP_AUTOCREATE_USERS:
+ assoc = ldap_create_user(user_info)
+ user = assoc.user
+ else:
+ return None
elif method == 'wordpress_site':
try:
diff --git a/askbot/deps/django_authopenid/ldap_auth.py b/askbot/deps/django_authopenid/ldap_auth.py
new file mode 100644
index 00000000..e48ec943
--- /dev/null
+++ b/askbot/deps/django_authopenid/ldap_auth.py
@@ -0,0 +1,177 @@
+import logging
+from django.conf import settings as django_settings
+from django.contrib.auth.models import User
+from django.forms import EmailField
+from askbot.conf import settings as askbot_settings
+from askbot.models.signals import user_registered
+
+LOG = logging.getLogger(__name__)
+
+def split_name(full_name, name_format):
+ bits = full_name.strip().split()
+ if len(bits) == 1:
+ bits.push('')
+ elif len(bits) == 0:
+ bits = ['', '']
+
+ if name_format == 'first,last':
+ return bits[0], bits[1]
+ elif name_format == 'last,first':
+ return bits[1], bits[0]
+ else:
+ raise ValueError('Unexpected value of name_format')
+
+
+def ldap_authenticate(username, password):
+ """
+ Authenticate using ldap
+
+ returns a dict with keys:
+
+ * first_name
+ * last_name
+ * ldap_username
+ * email (optional only if there is valid email)
+
+ python-ldap must be installed
+ http://pypi.python.org/pypi/python-ldap/2.4.6
+ """
+ import ldap
+ user_information = None
+ try:
+ ldap_session = ldap.initialize(askbot_settings.LDAP_URL)
+
+ #set protocol version
+ if askbot_settings.LDAP_PROTOCOL_VERSION == '2':
+ ldap_session.protocol_version = ldap.VERSION2
+ elif askbot_settings.LDAP_PROTOCOL_VERSION == '3':
+ ldap_session.protocol_version = ldap.VERSION3
+ else:
+ raise NotImplementedError('unsupported version of ldap protocol')
+
+ ldap.set_option(ldap.OPT_REFERRALS, 0)
+
+ #set extra ldap options, if given
+ if hasattr(django_settings, 'LDAP_EXTRA_OPTIONS'):
+ options = django_settings.LDAP_EXTRA_OPTIONS
+ for key, value in options:
+ if key.startswith('OPT_'):
+ ldap_key = getattr(ldap, key)
+ ldap.set_option(ldap_key, value)
+ else:
+ raise ValueError('Invalid LDAP option %s' % key)
+
+ #add optional "master" LDAP authentication, if required
+ master_username = getattr(django_settings, 'LDAP_LOGIN_DN', None)
+ master_password = getattr(django_settings, 'LDAP_PASSWORD', None)
+
+ login_name_field = askbot_settings.LDAP_LOGIN_NAME_FIELD
+ base_dn = askbot_settings.LDAP_BASE_DN
+ login_template = login_name_field + '=%s,' + base_dn
+ encoding = askbot_settings.LDAP_ENCODING
+
+ if master_username and master_password:
+ ldap_session.simple_bind_s(
+ master_username.encode(encoding),
+ master_password.encode(encoding)
+ )
+
+ user_filter = askbot_settings.LDAP_USER_FILTER_TEMPLATE % (
+ askbot_settings.LDAP_LOGIN_NAME_FIELD,
+ username
+ )
+
+ email_field = askbot_settings.LDAP_EMAIL_FIELD
+
+ get_attrs = [
+ email_field.encode(encoding),
+ login_name_field.encode(encoding)
+ #str(askbot_settings.LDAP_USERID_FIELD)
+ #todo: here we have a chance to get more data from LDAP
+ #maybe a point for some plugin
+ ]
+
+ common_name_field = askbot_settings.LDAP_COMMON_NAME_FIELD.strip()
+ given_name_field = askbot_settings.LDAP_GIVEN_NAME_FIELD.strip()
+ surname_field = askbot_settings.LDAP_SURNAME_FIELD.strip()
+
+ if given_name_field and surname_field:
+ get_attrs.append(given_name_field.encode(encoding))
+ get_attrs.append(surname_field.encode(encoding))
+ elif common_name_field:
+ get_attrs.append(common_name_field.encode(encoding))
+
+ # search ldap directory for user
+ user_search_result = ldap_session.search_s(
+ askbot_settings.LDAP_BASE_DN.encode(encoding),
+ ldap.SCOPE_SUBTREE,
+ user_filter.encode(encoding),
+ 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]
+ elif surname_field:
+ common_name_format = askbot_settings.LDAP_COMMON_NAME_FIELD_FORMAT
+ common_name = user_information.get(common_name_field, [''])[0]
+ first_name, last_name = split_name(common_name, common_name_format)
+
+ user_info = {
+ 'first_name': first_name,
+ 'last_name': last_name,
+ 'ldap_username': user_information[login_name_field][0]
+ }
+
+ try:
+ email = user_information.get(email_field, [''])[0]
+ user_info['email'] = EmailField().clean(email)
+ except ValidationError:
+ pass
+
+ return user_info
+ else:
+ return None
+
+ except ldap.INVALID_CREDENTIALS, e:
+ return None # Will fail login on return of None
+ except ldap.LDAPError, e:
+ LOG.error("LDAPError Exception")
+ LOG.exception(e)
+ return None
+ except Exception, e:
+ LOG.error("Unexpected Exception Occurred")
+ LOG.exception(e)
+ return None
+
+
+def ldap_create_user(user_info):
+ """takes the result returned by the :func:`ldap_authenticate`
+
+ and returns a :class:`UserAssociation` object
+ """
+ # create new user in local db
+ user = User()
+ user.username = user_info['ldap_username']
+ user.set_unusable_password()
+ user.first_name = user_info['first_name']
+ user.last_name = user_info['last_name']
+ user.email = user_info['email']
+ user.is_staff = False
+ user.is_superuser = False
+ user.is_active = True
+ user.save()
+ user_registered.send(None, user = user)
+ LOG.info('Created New User : [{0}]'.format(exact_username))
+
+ assoc = UserAssociation()
+ assoc.user = user
+ assoc.openid_url = user_info['ldap_username'] + '@ldap'
+ assoc.provider_name = 'ldap'
+ assoc.save()
+ return assoc
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 9b270230..47d8626f 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -48,6 +48,8 @@ from django.utils.safestring import mark_safe
from django.core.mail import send_mail
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 urlparse import urlparse
from openid.consumer.consumer import Consumer, \
@@ -312,25 +314,39 @@ def signin(request):
assert(password_action == 'login')
username = login_form.cleaned_data['username']
password = login_form.cleaned_data['password']
- # will be None if authentication fails
- #todo: since django 1.2 there is .exists()
- user_is_old = (User.objects.filter(username = username).count() > 0)
user = authenticate(
username=username,
password=password,
method = 'ldap'
)
- if user is not None:
- login(request, user)
- if user_is_old:
- return HttpResponseRedirect(next_url)
- else:
- return HttpResponseRedirect(reverse('verify_user_information'))
+ if user:
+ login(request, user)
+ return HttpResponseRedirect(next_url)
else:
- request.user.message_set.create(_('Incorrect user name or password'))
- return HttpResponseRedirect(request.path)
+ #try to login again via LDAP
+ user_info = ldap_authenticate(username, password)
+ if user_info:
+ if askbot_settings.LDAP_AUTOCREATE_USERS:
+ #create new user or
+ user = ldap_create_user(user_info).user
+ login(request, user)
+ return HttpResponseRedirect(next_url)
+ else:
+ #continue with proper registration
+ ldap_username = user_info['ldap_username']
+ return finalize_generic_signin(
+ request,
+ login_provider_name = 'ldap',
+ user_identifier = ldap_username + '@ldap',
+ redirect_url = next_url
+ )
+ else:
+ request.user.message_set.create(
+ _('Incorrect user name or password')
+ )
+ return HttpResponseRedirect(request.path)
else:
if password_action == 'login':
user = authenticate(
@@ -747,22 +763,21 @@ def finalize_generic_signin(
{'provider': login_provider_name}
request.user.message_set.create(message = msg)
return HttpResponseRedirect(redirect_url)
+ elif user:
+ #login branch
+ login(request, user)
+ logging.debug('login success')
+ return HttpResponseRedirect(redirect_url)
else:
- if user is None:
- #need to register
- request.method = 'GET'#this is not a good thing to do
- #but necessary at the moment to reuse the register()
- #method
- return register(
- request,
- login_provider_name=login_provider_name,
- user_identifier=user_identifier
- )
- else:
- #login branch
- login(request, user)
- logging.debug('login success')
- return HttpResponseRedirect(redirect_url)
+ #need to register
+ request.method = 'GET'#this is not a good thing to do
+ #but necessary at the moment to reuse the register()
+ #method
+ return register(
+ request,
+ login_provider_name=login_provider_name,
+ user_identifier=user_identifier
+ )
@login_required
@csrf.csrf_protect
@@ -820,6 +835,9 @@ def register(request, login_provider_name=None, user_identifier=None):
in which case request.method must ge 'GET'
and login_provider_name and user_identifier arguments must not be None
+ user_identifier will be stored in the UserAssociation as openid_url
+ login_provider_name - as provider_name
+
this function may need to be refactored to simplify the usage pattern
template : authopenid/complete.html