from .xlate import XLATOR
-__version__ = '0.1.0'
+__version__ = '0.2.0'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
DEFAULT_PORT_LDAP = 389
DEFAULT_PORT_LDAPS = 636
DEFAULT_TIMEOUT = 20
+MAX_TIMEOUT = 3600
# =============================================================================
class LdapConfigError(MultiConfigError):
self._port = DEFAULT_PORT_LDAP
self._base_dn = None
self._bind_dn = None
+ self._bind_pw = None
super(LdapConnectionInfo, self).__init__(
appname=appname, verbose=verbose, version=version, base_dir=base_dir,
self.use_ldaps = use_ldaps
self.port = port
self.base_dn = base_dn
- self.base_pw = base_pw
+ self.bind_dn = bind_dn
+ self.bind_pw = bind_pw
if initialized:
self.initialized = True
res['use_ldaps'] = self.use_ldaps
res['port'] = self.port
res['base_dn'] = self.base_dn
- res['base_pw'] = self.base_pw
+ res['bind_dn'] = self.bind_dn
+ res['bind_pw'] = None
res['schema'] = self.schema
res['url'] = self.url
+ if self.bind_pw:
+ if self.verbose > 4:
+ res['bind_pw'] = self.bind_pw
+ else:
+ res['bind_pw'] = '******'
+
return res
# -----------------------------------------------------------
raise LdapConfigError(_("Invalid port {!r} for LDAP server given.").format(value))
self._port = v
+ # -----------------------------------------------------------
+ @property
+ def base_dn(self):
+ """The DN used to connect to the LDAP server, anonymous bind is used, if
+ this DN is empty or None."""
+ return self._base_dn
+
+ @base_dn.setter
+ def base_dn(self, value):
+ if value is None or str(value).strip() == '':
+ msg = _("An empty Base DN for LDAP serches is not allowed.")
+ raise LdapConfigError(msg)
+ self._base_dn = str(value).strip()
+
# -----------------------------------------------------------
@property
def bind_dn(self):
fields.append("use_ldaps={!r}".format(self.use_ldaps))
fields.append("port={!r}".format(self.port))
fields.append("base_dn={!r}".format(self.base_dn))
- fields.append("base_pw={!r}".format(self.base_pw))
+ fields.append("bind_dn={!r}".format(self.bind_dn))
+ fields.append("bind_pw={!r}".format(self.bind_pw))
fields.append("initialized={!r}".format(self.initialized))
out += ", ".join(fields) + ")>"
return out
+ # -------------------------------------------------------------------------
+ def __copy__(self):
+
+ new = self.__class__(
+ appname=appname, verbose=verbose, base_dir=base_dir, host=host, use_ldaps=use_ldaps,
+ port=port, base_dn=base_dn, bind_dn=bind_dn, bind_pw=bind_pw,
+ initialized=initialized)
+
+ return new
+
+
+# =============================================================================
+class LdapConnectionDict(dict, FbGenericBaseObject):
+ """A dictionary containing LdapConnectionInfo as values and their names as keys."""
+
+ # -------------------------------------------------------------------------
+ def as_dict(self, short=True):
+ """
+ Transforms the elements of the object into a dict
+
+ @param short: don't include local properties in resulting dict.
+ @type short: bool
+
+ @return: structure as dict
+ @rtype: dict
+ """
+
+ res = super(LdapConnectionDict, self).as_dict(short=short)
+
+ for key in self.keys():
+ res[key] = self[key].as_dict(short=short)
+
+ return res
+
+ # -------------------------------------------------------------------------
+ def __copy__(self):
+
+ new = self.__class__()
+
+ for key in self.keys():
+ new[key] = copy.copy(self.[key])
+
+ return new
+
+
+# =============================================================================
+class LdapConfiguration(BaseMultiConfig):
+ """
+ A class for providing a configuration for an arbitrary Application working
+ with one or more LDAP connections, and methods to read it from configuration files.
+ """
+
+ default_ldap_server = 'prd-ds.pixelpark.com'
+ use_ssl_on_default = True
+ default_ldap_port = DEFAULT_PORT_LDAPS
+ default_base_dn = 'o=isp'
+ default_bind_dn = 'uid=readonly,ou=People,o=isp'
+
+ re_ldap_section_w_name = re.compile(r'^\s*ldap\s*:\s*(\S+)')
+
+ re_ldap_host_key = re.compile(r'^\s*(?:host|server)\s*$', re.IGNORECASE)
+ re_ldap_ldaps_key = re.compile(r'^\s*(?:use[_-]?)?(?:ldaps|ssl)\s*$', re.IGNORECASE)
+ re_ldap_port_key = re.compile(r'^\s*port\s*$', re.IGNORECASE)
+ re_ldap_base_dn_key = re.compile(r'^\s*base[_-]*dn\s*$', re.IGNORECASE)
+ re_ldap_bind_dn_key = re.compile(r'^\s*bind[_-]*dn\s*$', re.IGNORECASE)
+ re_ldap_bind_pw_key = re.compile(r'^\s*bind[_-]*pw\s*$', re.IGNORECASE)
+
+ # -------------------------------------------------------------------------
+ def __init__(
+ self, appname=None, verbose=0, version=__version__, base_dir=None,
+ append_appname_to_stems=True, additional_stems=None, config_dir=DEFAULT_CONFIG_DIR,
+ additional_config_file=None, additional_cfgdirs=None, encoding=DEFAULT_ENCODING,
+ ensure_privacy=False, use_chardet=True, initialized=False):
+
+ add_stems = []
+ if additional_stems:
+ if is_sequence(additional_stems):
+ for stem in additional_stems:
+ add_stems.append(stem)
+ else:
+ add_stems.append(additional_stems)
+
+ if 'ldap' not in add_stems:
+ add_stems.append('ldap')
+
+ self.timeout = DEFAULT_TIMEOUT
+
+ super(LdapConfiguration, self).__init__(
+ appname=appname, verbose=verbose, version=version, base_dir=base_dir,
+ append_appname_to_stems=append_appname_to_stems, config_dir=config_dir,
+ additional_stems=add_stems, additional_config_file=additional_config_file,
+ additional_cfgdirs=additional_cfgdirs, encoding=encoding, use_chardet=use_chardet,
+ ensure_privacy=ensure_privacy, initialized=False,
+ )
+
+ self.connection = LdapConnectionDict()
+
+ default_connection = LdapConnectionInfo(
+ appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+ host=self.default_ldap_server, use_ldaps=self.use_ssl_on_default,
+ port=self.default_ldap_port, base_dn=self.default_base_dn,
+ bind_dn=self.default_bind_dn, initialized=False)
+
+ self.connection['default'] = default_connection
+
+ # -------------------------------------------------------------------------
+ def eval_section(self, section_name):
+
+ super(LdapConfiguration, self).eval_section(section_name)
+
+ sn = section_name.lower()
+ section = self.cfg[section_name]
+
+ if sn == 'ldap':
+ for key in section.keys():
+ sub = section[key]
+ if key.lower.strip() == 'timeout':
+ self._eval_timeout(sub)
+ continue
+ self._eval_ldap_connection(key, sub)
+ return
+
+ match = self.re_ldap_section_w_name.match(sn)
+ if match:
+ connection_name = match.group(1)
+ self._eval_ldap_connection(connection_name, section)
+
+ # -------------------------------------------------------------------------
+ def _eval_timeout(self, value):
+
+ timeout = DEFAULT_TIMEOUT
+
+ try:
+ timeout = int(value)
+ except (ValueError, TypeError) as e:
+ msg = _("Value {!r} for a timeout is invalid:").format(value)
+ msg += ' ' + str(e)
+ LOG.error(msg)
+ continue
+ if timeout <= 0 or timeout > MAX_TIMEOUT:
+ msg = _("Value {!r} for a timeout is invalid:").format(value)
+ msg += ' ' + str(e)
+ LOG.error(msg)
+ continue
+
+ self.timeout = timeout
+
+ # -------------------------------------------------------------------------
+ def _eval_ldap_connection(self, connection_name, section):
+
+ connection = LdapConnectionInfo(
+ appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+ initialized=False)
+
+ section_name = "ldap:" + connection_name
+ msg_invalid = _("Invalid value {val!r} in section {sec!r} for a LDAP {what}.")
+
+ for key in section.keys():
+
+ value = section[key]
+
+ if self.re_ldap_host_key.match(key)
+ if value.strip():
+ connection.host = value
+ else:
+ msg = msg_invalid.format(val=value, sec=section_name, what='host')
+ LOG.error(msg)
+ continue
+
+ if self.re_ldap_ldaps_key.match(key):
+ connection.use_ldaps = value
+ continue
+
+ if self.re_ldap_port_key.match(key):
+ port = DEFAULT_PORT_LDAP
+ try:
+ port = int(value)
+ except (ValueError, TypeError) as e:
+ msg = msg_invalid.format(val=value, sec=section_name, what='port')
+ msg += ' ' + str(e)
+ LOG.error(msg)
+ continue
+ if port <= 0 or port > MAX_PORT_NUMBER:
+ msg = msg_invalid.format(val=value, sec=section_name, what='port')
+ msg += ' ' + str(e)
+ LOG.error(msg)
+ continue
+ connection.port = port
+
+ if self.re_ldap_base_dn_key.match(key):
+ if value.strip():
+ connection.base_dn = value
+ else:
+ msg = msg_invalid.format(val=value, sec=section_name, what='base_dn')
+ LOG.error(msg)
+ continue
+
+ if self.re_ldap_bind_dn_key.match(key):
+ connection.bind_dn = value
+ continue
+
+ if self.re_ldap_bind_pw.match(key):
+ connection.bind_pw = value
+ continue
+
+ msg = _("Unknown LDAP configuration key found in section {!r}.").format(section_name)
+ LOG.error(msg)
+
+ self.connection[connection_name] = connection
+
# =============================================================================
if __name__ == "__main__":