#!/usr/bin/env python # encoding: utf-8 # # This file is part of ckanext-ldap # Created by the Natural History Museum in London, UK import logging import uuid import ldap import ldap.filter import re from ckanext.ldap.lib.exceptions import UserConflictError from ckanext.ldap.model.ldap_user import LdapUser from ckan.common import session from ckan.model import Session from ckan.plugins import toolkit log = logging.getLogger(__name__) def login_failed(notice=None, error=None): '''Handle login failures Redirect to /user/login and flash an optional message :param notice: Optional notice for the user (Default value = None) :param error: Optional error message for the user (Default value = None) ''' if notice: toolkit.h.flash_notice(notice) if error: toolkit.h.flash_error(error) return toolkit.redirect_to(u'user.login') def login_success(user_name, came_from): '''Handle login success Saves the user in the session and redirects to user/logged_in :param user_name: The user name ''' session[u'ckanext-ldap-user'] = user_name session.save() return toolkit.redirect_to(u'user.logged_in', came_from=came_from) def get_user_dict(user_id): """Calls the action API to get the detail for a user given their id @param user_id: The user id """ context = { u'ignore_auth': True } data_dict = { u'id': user_id } return toolkit.get_action(u'user_show')(context, data_dict) def ckan_user_exists(user_name): '''Check if a CKAN user name exists, and if that user is an LDAP user. :param user_name: User name to check :returns: Dictionary defining 'exists' and 'ldap'. ''' try: user = get_user_dict(user_name) except toolkit.ObjectNotFound: return { u'exists': False, u'is_ldap': False } ldap_user = LdapUser.by_user_id(user[u'id']) if ldap_user: return { u'exists': True, u'is_ldap': True } else: return { u'exists': True, u'is_ldap': False } def get_unique_user_name(base_name): '''Create a unique, valid, non existent user name from the given base name :param base_name: Base name :returns: A valid user name not currently in use based on base_name ''' base_name = re.sub(u'[^-a-z0-9_]', u'_', base_name.lower()) base_name = base_name[0:100] if len(base_name) < 2: base_name = (base_name + u'__')[0:2] count = 0 user_name = base_name while (ckan_user_exists(user_name))[u'exists']: count += 1 user_name = u'{base}{count}'.format(base=base_name[0:100 - len(str(count))], count=str(count)) return user_name def get_or_create_ldap_user(ldap_user_dict): '''Get or create a CKAN user from the data returned by the LDAP server :param ldap_user_dict: Dictionary as returned by _find_ldap_user :returns: The CKAN username of an existing user ''' # Look for existing user, and if found return it. ldap_user = LdapUser.by_ldap_id(ldap_user_dict[u'username']) if ldap_user: # TODO: Update the user detail. return ldap_user.user.name user_dict = {} update = False # Check whether we have a name conflict (based on the ldap name, without mapping # it to allowed chars) exists = ckan_user_exists(ldap_user_dict[u'username']) if exists[u'exists'] and not exists[u'is_ldap']: # If ckanext.ldap.migrate is set, update exsting user_dict. if not toolkit.config[u'ckanext.ldap.migrate']: raise UserConflictError(toolkit._( u'There is a username conflict. Please inform the site administrator.')) else: user_dict = get_user_dict(ldap_user_dict[u'username']) update = True # If a user with the same ckan name already exists but is an LDAP user, this means # (given that we didn't find it above) that the conflict arises from having mangled # another user's LDAP name. There will not however be a conflict based on what is # entered in the user prompt - so we can go ahead. The current user's id will just # be mangled to something different. # Now get a unique user name (if not "migrating"), and create the CKAN user and # the LdapUser entry. user_name = user_dict[u'name'] if update else get_unique_user_name( ldap_user_dict[u'username']) user_dict.update({ u'name': user_name, u'email': ldap_user_dict[u'email'], u'password': str(uuid.uuid4()) }) if u'fullname' in ldap_user_dict: user_dict[u'fullname'] = ldap_user_dict[u'fullname'] if u'about' in ldap_user_dict: user_dict[u'about'] = ldap_user_dict[u'about'] if update: ckan_user = toolkit.get_action(u'user_update')( context={ u'ignore_auth': True }, data_dict=user_dict ) else: ckan_user = toolkit.get_action(u'user_create')( context={ u'ignore_auth': True }, data_dict=user_dict ) ldap_user = LdapUser(user_id=ckan_user[u'id'], ldap_id=ldap_user_dict[u'username']) Session.add(ldap_user) Session.commit() # Add the user to it's group if needed if u'ckanext.ldap.organization.id' in toolkit.config: toolkit.get_action(u'member_create')( context={ u'ignore_auth': True }, data_dict={ u'id': toolkit.config[u'ckanext.ldap.organization.id'], u'object': user_name, u'object_type': u'user', u'capacity': toolkit.config[u'ckanext.ldap.organization.role'] } ) return user_name def check_ldap_password(cn, password): '''Checks that the given cn/password credentials work on the given CN. :param cn: Common name to log on :param password: Password for cn :returns: True on success, False on failure ''' cnx = ldap.initialize(toolkit.config[u'ckanext.ldap.uri'], bytes_mode=False, trace_level=toolkit.config[u'ckanext.ldap.trace_level']) try: cnx.bind_s(cn, password) except ldap.SERVER_DOWN: log.error(u'LDAP server is not reachable') return False except ldap.INVALID_CREDENTIALS: log.debug(u'Invalid LDAP credentials') return False # Fail on empty password if password == u'': log.debug(u'Invalid LDAP credentials') return False cnx.unbind_s() return True