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.auths.Exceptions import AuthenticatorException
from uds.core.util import tools
from uds.core.util import ldaputil
import six
import ldap
@ -45,7 +46,7 @@ import ldap.filter
import re
import logging
__updated__ = '2018-01-15'
__updated__ = '2018-01-24'
logger = logging.getLogger(__name__)
@ -145,7 +146,7 @@ class RegexLdap(auths.Authenticator):
attr = line[:equalPos]
else:
attr = line
res.append(tools.b2(attr))
res.append(attr)
return res
def __processField(self, field, attributes):
@ -167,10 +168,8 @@ class RegexLdap(auths.Authenticator):
logger.debug('Pattern: {0}'.format(pattern))
for vv in val:
for v in val:
try:
v = tools.b2(vv)
logger.debug('v, vv: {}, {}'.format(v, vv))
srch = re.search(pattern, v, re.IGNORECASE)
logger.debug("Found against {0}: {1} ".format(v, srch.groups()))
if srch is None:
@ -178,6 +177,7 @@ class RegexLdap(auths.Authenticator):
res.append(''.join(srch.groups()))
except Exception:
pass # Ignore exceptions here
logger.debug('Res: {}'.format(res))
return res
def valuesDict(self):
@ -225,93 +225,36 @@ class RegexLdap(auths.Authenticator):
self._groupNameAttr, self._userNameAttr, self._altClass = data[1:]
self._ssl = gui.strToBool(self._ssl)
def __connection(self, username=None, password=None):
if self._connection is None or username is not None: # We want this method also to check credentials
l = None
cache = False
try:
if password is not None:
password = password.encode('utf-8')
def __connection(self):
"""
Tries to connect to ldap. If username is None, it tries to connect using user provided credentials.
@return: Connection established
@raise exception: If connection could not be established
"""
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
def __connectAs(self, username, password):
return ldaputil.connection(username, password, self._host, ssl=self._ssl, timeout=self._timeout, debug=False)
def __getUser(self, username):
username = ldap.filter.escape_filter_chars(tools.b2(username))
try:
con = self.__connection()
filter_ = tools.b2('(&(objectClass={})({}={}))'.format(self._userClass, self._userIdAttr, username))
attrlist = [tools.b2(self._userIdAttr)] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr)
logger.debug('Getuser filter_: {}, attr list: {}'.format(filter_, attrlist))
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
logger.debug('Res: {}'.format(res))
if res[0] is None:
return None
usr = dict((tools.u2(k.lower()), ['']) for k in attrlist)
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
"""
Searchs for the username and returns its LDAP entry
@param username: username to search, using user provided parameters at configuration to map search entries.
@return: None if username is not found, an dictionary of LDAP entry attributes if found.
@note: Active directory users contains the groups it belongs to in "memberOf" attribute
"""
return ldaputil.getFirst(
con=self.__connection(),
base=self._ldapBase,
objectClass=self._userClass,
field=self._userIdAttr,
value=username,
attributes=[self._userIdAttr] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr),
sizeLimit=LDAP_RESULT_LIMIT
)
def __getGroups(self, usr):
return self.__processField(self._groupNameAttr, usr)
@ -337,7 +280,7 @@ class RegexLdap(auths.Authenticator):
return False
# 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))
@ -406,15 +349,17 @@ class RegexLdap(auths.Authenticator):
try:
con = self.__connection()
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):
if r[0] is not None: # Must have a dn, we do not accept references to other
dct = {k.lower(): v for k, v in six.iteritems(r[1])}
logger.debug('R: {0}'.format(dct))
usrId = dct.get(self._userIdAttr.lower(), '')
usrId = type(usrId) == list and usrId[0] or usrId
for r in ldaputil.getAsDict(
con=self.__connection(),
base=self._ldapBase,
ldapFilter='(&(&(objectClass={})({}={}*))(objectCategory=person))'.format(self._userClass, self._userIdAttr, ldaputil.escape(pattern)),
attrList=None, # All attrs
sizeLimit=LDAP_RESULT_LIMIT
):
logger.debug('R: {0}'.format(r))
res.append({
'id': usrId,
'name': self.__getUserRealName(dct)
'id': r.get(self._userIdAttr.lower(), '')[0],
'name': self.__getUserRealName(r)
})
logger.debug(res)
return res

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))
if attrList is not None:
attrList = [tools.b2(i) for i in attrList]
try:
# On python2, attrs and search string is str (not unicode), in 3, str (not bytes)
res = con.search_ext_s(base,
scope=scope,
filterstr=tools.b2(ldapFilter),
attrlist=[tools.b2(i) for i in attrList],
attrlist=attrList,
sizelimit=sizeLimit
)
@ -111,11 +114,11 @@ def getAsDict(con, base, ldapFilter, attrList, sizeLimit, scope=ldap.SCOPE_SUBTR
continue # Skip None entities
# 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
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]})
@ -125,7 +128,7 @@ def getAsDict(con, base, ldapFilter, attrList, sizeLimit, scope=ldap.SCOPE_SUBTR
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
@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 six
__updated__ = '2018-01-15'
__updated__ = '2018-01-24'
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):
"""
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: [
//'babel',
'sass',
'imagemin',
'svgmin'
//'imagemin',
//'svgmin'
]
}
});

View File

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