Updated LDAP REGEX to be compatible with python2 & pytohn3

This commit is contained in:
Adolfo Gómez García 2018-01-24 10:28:26 +01:00
parent 789084d3ed
commit 8a06132b1a
6 changed files with 111 additions and 119 deletions

View File

@ -38,6 +38,7 @@ from uds.core.ui.UserInterface import gui
from uds.core import auths from uds.core import auths
from uds.core.auths.Exceptions import AuthenticatorException from uds.core.auths.Exceptions import AuthenticatorException
from uds.core.util import tools from uds.core.util import tools
from uds.core.util import ldaputil
import six import six
import ldap import ldap
@ -45,7 +46,7 @@ import ldap.filter
import re import re
import logging import logging
__updated__ = '2018-01-15' __updated__ = '2018-01-24'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -145,7 +146,7 @@ class RegexLdap(auths.Authenticator):
attr = line[:equalPos] attr = line[:equalPos]
else: else:
attr = line attr = line
res.append(tools.b2(attr)) res.append(attr)
return res return res
def __processField(self, field, attributes): def __processField(self, field, attributes):
@ -167,10 +168,8 @@ class RegexLdap(auths.Authenticator):
logger.debug('Pattern: {0}'.format(pattern)) logger.debug('Pattern: {0}'.format(pattern))
for vv in val: for v in val:
try: try:
v = tools.b2(vv)
logger.debug('v, vv: {}, {}'.format(v, vv))
srch = re.search(pattern, v, re.IGNORECASE) srch = re.search(pattern, v, re.IGNORECASE)
logger.debug("Found against {0}: {1} ".format(v, srch.groups())) logger.debug("Found against {0}: {1} ".format(v, srch.groups()))
if srch is None: if srch is None:
@ -178,6 +177,7 @@ class RegexLdap(auths.Authenticator):
res.append(''.join(srch.groups())) res.append(''.join(srch.groups()))
except Exception: except Exception:
pass # Ignore exceptions here pass # Ignore exceptions here
logger.debug('Res: {}'.format(res))
return res return res
def valuesDict(self): def valuesDict(self):
@ -225,93 +225,36 @@ class RegexLdap(auths.Authenticator):
self._groupNameAttr, self._userNameAttr, self._altClass = data[1:] self._groupNameAttr, self._userNameAttr, self._altClass = data[1:]
self._ssl = gui.strToBool(self._ssl) self._ssl = gui.strToBool(self._ssl)
def __connection(self, username=None, password=None): def __connection(self):
if self._connection is None or username is not None: # We want this method also to check credentials """
l = None Tries to connect to ldap. If username is None, it tries to connect using user provided credentials.
cache = False @return: Connection established
try: @raise exception: If connection could not be established
if password is not None: """
password = password.encode('utf-8') if self._connection is None: # We want this method also to check credentials
self._connection = ldaputil.connection(self._username, self._password, self._host, ssl=self._ssl, timeout=self._timeout, debug=False)
# ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9)
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
schema = self._ssl and 'ldaps' or 'ldap'
port = self._port != '389' and ':' + self._port or ''
uri = "%s://%s%s" % (schema, self._host, port)
logger.debug('Ldap uri: {0}'.format(uri))
l = ldap.initialize(uri=uri)
l.set_option(ldap.OPT_REFERRALS, 0)
l.network_timeout = l.timeout = int(self._timeout)
l.protocol_version = ldap.VERSION3
if username is None:
cache = True
username = self._username
password = self._password
l.simple_bind_s(who=username, cred=password)
except ldap.LDAPError as e:
str_ = _('Ldap connection error: ')
if hasattr(e, 'message') and isinstance(e.message, dict):
str_ += ', '.join((e.message.get('info', ''), e.message.get('desc')))
else:
str_ += six.text_type(e)
raise Exception(str_)
if cache is True:
self._connection = l
else:
return l # Do not cache nor overwrite "global" connection
return self._connection return self._connection
def __connectAs(self, username, password):
return ldaputil.connection(username, password, self._host, ssl=self._ssl, timeout=self._timeout, debug=False)
def __getUser(self, username): def __getUser(self, username):
username = ldap.filter.escape_filter_chars(tools.b2(username)) """
try: Searchs for the username and returns its LDAP entry
con = self.__connection() @param username: username to search, using user provided parameters at configuration to map search entries.
filter_ = tools.b2('(&(objectClass={})({}={}))'.format(self._userClass, self._userIdAttr, username)) @return: None if username is not found, an dictionary of LDAP entry attributes if found.
attrlist = [tools.b2(self._userIdAttr)] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr) @note: Active directory users contains the groups it belongs to in "memberOf" attribute
"""
logger.debug('Getuser filter_: {}, attr list: {}'.format(filter_, attrlist)) return ldaputil.getFirst(
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, con=self.__connection(),
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0] base=self._ldapBase,
objectClass=self._userClass,
logger.debug('Res: {}'.format(res)) field=self._userIdAttr,
if res[0] is None: value=username,
return None attributes=[self._userIdAttr] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr),
usr = dict((tools.u2(k.lower()), ['']) for k in attrlist) sizeLimit=LDAP_RESULT_LIMIT
for k, v in six.iteritems(res[1]): )
usr[tools.u2(k.lower())] = list(i.decode('utf8') for i in v)
usr.update({'dn': res[0], '_id': username})
# If altClass
if self._altClass is not None and self._altClass != '':
logger.debug('Has alt class {}'.format(self._altClass))
filter_ = tools.b2('(&(objectClass={})({}={}))'.format(self._altClass, self._userIdAttr, username))
logger.debug('Get Alternate list filter: {}, attrlist: {}'.format(filter_, attrlist))
# Get alternate class objects
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)
for r in res:
if r[0] is None:
continue
logger.debug('*** Item: {}'.format(r))
for k, v in six.iteritems(r[1]):
kl = tools.b2(k.lower())
# If already exists the field
if kl in usr:
# Now append to existing values
for x in v:
usr[kl].append(x.decode('utf8'))
else:
usr[kl] = list(i.decode('utf8') for i in v)
logger.debug('Usr: {0}'.format(usr))
return usr
except Exception:
logger.exception('Exception:')
return None
def __getGroups(self, usr): def __getGroups(self, usr):
return self.__processField(self._groupNameAttr, usr) return self.__processField(self._groupNameAttr, usr)
@ -337,7 +280,7 @@ class RegexLdap(auths.Authenticator):
return False return False
# Let's see first if it credentials are fine # Let's see first if it credentials are fine
self.__connection(usr['dn'], credentials) # Will raise an exception if it can't connect self.__connectAs(usr['dn'], credentials) # Will raise an exception if it can't connect
groupsManager.validate(self.__getGroups(usr)) groupsManager.validate(self.__getGroups(usr))
@ -406,16 +349,18 @@ class RegexLdap(auths.Authenticator):
try: try:
con = self.__connection() con = self.__connection()
res = [] res = []
for r in con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT): for r in ldaputil.getAsDict(
if r[0] is not None: # Must have a dn, we do not accept references to other con=self.__connection(),
dct = {k.lower(): v for k, v in six.iteritems(r[1])} base=self._ldapBase,
logger.debug('R: {0}'.format(dct)) ldapFilter='(&(&(objectClass={})({}={}*))(objectCategory=person))'.format(self._userClass, self._userIdAttr, ldaputil.escape(pattern)),
usrId = dct.get(self._userIdAttr.lower(), '') attrList=None, # All attrs
usrId = type(usrId) == list and usrId[0] or usrId sizeLimit=LDAP_RESULT_LIMIT
res.append({ ):
'id': usrId, logger.debug('R: {0}'.format(r))
'name': self.__getUserRealName(dct) res.append({
}) 'id': r.get(self._userIdAttr.lower(), '')[0],
'name': self.__getUserRealName(r)
})
logger.debug(res) logger.debug(res)
return res return res
except Exception: except Exception:

View File

@ -94,12 +94,15 @@ def getAsDict(con, base, ldapFilter, attrList, sizeLimit, scope=ldap.SCOPE_SUBTR
""" """
logger.debug('Filter: {}, attr list: {}'.format(ldapFilter, attrList)) logger.debug('Filter: {}, attr list: {}'.format(ldapFilter, attrList))
if attrList is not None:
attrList = [tools.b2(i) for i in attrList]
try: try:
# On python2, attrs and search string is str (not unicode), in 3, str (not bytes) # On python2, attrs and search string is str (not unicode), in 3, str (not bytes)
res = con.search_ext_s(base, res = con.search_ext_s(base,
scope=scope, scope=scope,
filterstr=tools.b2(ldapFilter), filterstr=tools.b2(ldapFilter),
attrlist=[tools.b2(i) for i in attrList], attrlist=attrList,
sizelimit=sizeLimit sizelimit=sizeLimit
) )
@ -111,11 +114,11 @@ def getAsDict(con, base, ldapFilter, attrList, sizeLimit, scope=ldap.SCOPE_SUBTR
continue # Skip None entities continue # Skip None entities
# Convert back attritutes to test_type ONLY on python2 # Convert back attritutes to test_type ONLY on python2
dct = dict((k, ['']) for k in attrList) dct = tools.CaseInsensitiveDict((k, ['']) for k in attrList) if attrList is not None else tools.CaseInsensitiveDict()
# Convert back result fields to str # Convert back result fields to str
for k, v in six.iteritems(r[1]): for k, v in six.iteritems(r[1]):
dct[tools.u2(k)] = list(i.decode('utf8') for i in v) dct[tools.u2(k)] = list(i.decode('utf8', errors='replace') for i in v)
dct.update({'dn': r[0]}) dct.update({'dn': r[0]})
@ -125,7 +128,7 @@ def getAsDict(con, base, ldapFilter, attrList, sizeLimit, scope=ldap.SCOPE_SUBTR
logger.exception('Exception:') logger.exception('Exception:')
def getFirst(con, base, objectClass, field, value, attributes, sizeLimit=50): def getFirst(con, base, objectClass, field, value, attributes=None, sizeLimit=50):
""" """
Searchs for the username and returns its LDAP entry Searchs for the username and returns its LDAP entry
@param username: username to search, using user provided parameters at configuration to map search entries. @param username: username to search, using user provided parameters at configuration to map search entries.

View File

@ -42,7 +42,7 @@ import sys
import os import os
import six import six
__updated__ = '2018-01-15' __updated__ = '2018-01-24'
class DictAsObj(object): class DictAsObj(object):
@ -65,6 +65,50 @@ class DictAsObj(object):
) )
class CaseInsensitiveDict(dict):
@classmethod
def _k(cls, key):
return key.lower() if isinstance(key, six.text_type) else key
def __init__(self, *args, **kwargs):
super(CaseInsensitiveDict, self).__init__(*args, **kwargs)
self._convert_keys()
def __getitem__(self, key):
return super(CaseInsensitiveDict, self).__getitem__(self.__class__._k(key))
def __setitem__(self, key, value):
super(CaseInsensitiveDict, self).__setitem__(self.__class__._k(key), value)
def __delitem__(self, key):
return super(CaseInsensitiveDict, self).__delitem__(self.__class__._k(key))
def __contains__(self, key):
return super(CaseInsensitiveDict, self).__contains__(self.__class__._k(key))
def has_key(self, key):
return super(CaseInsensitiveDict, self).has_key(self.__class__._k(key))
def pop(self, key, *args, **kwargs):
return super(CaseInsensitiveDict, self).pop(self.__class__._k(key), *args, **kwargs)
def get(self, key, *args, **kwargs):
return super(CaseInsensitiveDict, self).get(self.__class__._k(key), *args, **kwargs)
def setdefault(self, key, *args, **kwargs):
return super(CaseInsensitiveDict, self).setdefault(self.__class__._k(key), *args, **kwargs)
def update(self, E={}, **F):
super(CaseInsensitiveDict, self).update(self.__class__(E))
super(CaseInsensitiveDict, self).update(self.__class__(**F))
def _convert_keys(self):
for k in list(self.keys()):
v = super(CaseInsensitiveDict, self).pop(k)
self.__setitem__(k, v)
def packageRelativeFile(moduleName, fileName): def packageRelativeFile(moduleName, fileName):
""" """
Helper to get image path from relative to a module. Helper to get image path from relative to a module.

File diff suppressed because one or more lines are too long

View File

@ -362,8 +362,8 @@ module.exports = function (grunt) {
dist: [ dist: [
//'babel', //'babel',
'sass', 'sass',
'imagemin', //'imagemin',
'svgmin' //'svgmin'
] ]
} }
}); });

View File

@ -1,31 +1,31 @@
{ {
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.0.2", "autoprefixer": "^6.7.7",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-babel": "^5.0.0", "grunt-babel": "^5.0.0",
"grunt-browser-sync": "^2.1.2", "grunt-browser-sync": "^2.2.0",
"grunt-concurrent": "^1.0.0", "grunt-concurrent": "^1.0.0",
"grunt-contrib-clean": "^0.6.0", "grunt-contrib-clean": "^0.6.0",
"grunt-contrib-concat": "^0.5.1", "grunt-contrib-concat": "^0.5.1",
"grunt-contrib-copy": "^0.8.0", "grunt-contrib-copy": "^0.8.2",
"grunt-contrib-cssmin": "^0.12.2", "grunt-contrib-cssmin": "^0.12.2",
"grunt-contrib-htmlmin": "^0.4.0", "grunt-contrib-htmlmin": "^0.4.0",
"grunt-contrib-imagemin": "^0.9.3", "grunt-contrib-imagemin": "^0.9.3",
"grunt-contrib-uglify": "^0.8.0", "grunt-contrib-uglify": "^0.8.0",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^0.6.1",
"grunt-eslint": "^17.0.0", "grunt-eslint": "^17.3.2",
"grunt-filerev": "^2.2.0", "grunt-filerev": "^2.2.0",
"grunt-mocha": "^0.4.12", "grunt-mocha": "^0.4.15",
"grunt-modernizr": "^0.6.0", "grunt-modernizr": "^0.6.1",
"grunt-newer": "^1.1.0", "grunt-newer": "^1.3.0",
"grunt-postcss": "^0.6.0", "grunt-postcss": "^0.6.0",
"grunt-sass": "^1.0.0", "grunt-sass": "^1.2.1",
"grunt-svgmin": "^2.0.1", "grunt-svgmin": "^2.0.1",
"grunt-usemin": "^3.0.0", "grunt-usemin": "^3.0.0",
"grunt-wiredep": "^2.0.0", "grunt-wiredep": "^2.0.0",
"jit-grunt": "^0.9.1", "jit-grunt": "^0.9.1",
"time-grunt": "^1.1.0" "time-grunt": "^1.4.0"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"