2015-04-15 21:13:14 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vim:set expandtab tabstop=4 shiftwidth=4:
|
|
|
|
#
|
|
|
|
# The MIT License (MIT)
|
|
|
|
# LdapCherry
|
|
|
|
# Copyright (c) 2014 Carpentier Pierre-Francois
|
|
|
|
|
2015-05-20 17:13:18 +02:00
|
|
|
import cherrypy
|
|
|
|
import ldap
|
2015-05-26 00:33:36 +02:00
|
|
|
import ldap.modlist as modlist
|
2015-07-15 21:28:54 +02:00
|
|
|
import ldap.filter
|
2015-05-20 17:13:18 +02:00
|
|
|
import logging
|
2015-05-20 14:21:43 +02:00
|
|
|
import ldapcherry.backend
|
2019-02-06 22:32:40 +01:00
|
|
|
import sys
|
2015-07-31 22:43:51 +02:00
|
|
|
from ldapcherry.exceptions import UserDoesntExist, \
|
|
|
|
GroupDoesntExist, \
|
|
|
|
UserAlreadyExists
|
2015-07-05 22:01:09 +02:00
|
|
|
import os
|
2015-05-26 00:33:36 +02:00
|
|
|
import re
|
2019-02-07 20:16:39 +01:00
|
|
|
if sys.version < '3':
|
|
|
|
from sets import Set as set
|
2015-05-26 00:33:36 +02:00
|
|
|
|
2015-07-10 21:06:28 +02:00
|
|
|
|
2015-07-05 22:01:09 +02:00
|
|
|
class CaFileDontExist(Exception):
|
|
|
|
def __init__(self, cafile):
|
|
|
|
self.cafile = cafile
|
2015-11-03 08:53:46 +01:00
|
|
|
self.log = "CA file %(cafile)s does not exist" % {'cafile': cafile}
|
2015-07-05 22:01:09 +02:00
|
|
|
|
2017-03-16 02:45:23 +01:00
|
|
|
|
|
|
|
class MissingGroupAttr(Exception):
|
|
|
|
def __init__(self, attr):
|
|
|
|
self.attr = attr
|
|
|
|
self.log = "User doesn't have %(attr)s in its attributes" \
|
|
|
|
", cannot use it to set group" % {'attr': attr}
|
|
|
|
|
|
|
|
|
|
|
|
class MultivaluedGroupAttr(Exception):
|
|
|
|
def __init__(self, attr):
|
|
|
|
self.attr = cafile
|
|
|
|
self.log = "User's attribute '%(attr)s' is multivalued" \
|
|
|
|
", cannot use it to set group" % {'attr': attr}
|
|
|
|
|
|
|
|
|
2015-07-10 21:06:28 +02:00
|
|
|
NO_ATTR = 0
|
2015-06-16 21:29:40 +02:00
|
|
|
DISPLAYED_ATTRS = 1
|
|
|
|
LISTED_ATTRS = 2
|
|
|
|
ALL_ATTRS = 3
|
|
|
|
|
2015-07-10 21:06:28 +02:00
|
|
|
|
2015-04-15 21:13:14 +02:00
|
|
|
class Backend(ldapcherry.backend.Backend):
|
|
|
|
|
2015-05-31 18:40:35 +02:00
|
|
|
def __init__(self, config, logger, name, attrslist, key):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Backend initialization"""
|
|
|
|
# Recover all the parameters
|
2015-05-20 17:13:18 +02:00
|
|
|
self.config = config
|
2015-05-21 08:52:06 +02:00
|
|
|
self._logger = logger
|
2015-05-21 19:55:11 +02:00
|
|
|
self.backend_name = name
|
2015-07-31 20:08:21 +02:00
|
|
|
self.backend_display_name = self.get_param('display_name')
|
2015-05-21 21:40:13 +02:00
|
|
|
self.binddn = self.get_param('binddn')
|
2015-05-22 01:16:53 +02:00
|
|
|
self.bindpassword = self.get_param('password')
|
2015-05-21 21:40:13 +02:00
|
|
|
self.ca = self.get_param('ca', False)
|
|
|
|
self.checkcert = self.get_param('checkcert', 'on')
|
|
|
|
self.starttls = self.get_param('starttls', 'off')
|
|
|
|
self.uri = self.get_param('uri')
|
2015-05-22 20:05:24 +02:00
|
|
|
self.timeout = self.get_param('timeout', 1)
|
2015-05-22 01:16:53 +02:00
|
|
|
self.userdn = self.get_param('userdn')
|
|
|
|
self.groupdn = self.get_param('groupdn')
|
2015-05-21 21:40:13 +02:00
|
|
|
self.user_filter_tmpl = self.get_param('user_filter_tmpl')
|
2015-05-25 18:52:14 +02:00
|
|
|
self.group_filter_tmpl = self.get_param('group_filter_tmpl')
|
|
|
|
self.search_filter_tmpl = self.get_param('search_filter_tmpl')
|
2015-05-26 00:33:36 +02:00
|
|
|
self.dn_user_attr = self.get_param('dn_user_attr')
|
|
|
|
self.objectclasses = []
|
2015-05-31 18:40:35 +02:00
|
|
|
self.key = key
|
2015-07-29 08:15:54 +02:00
|
|
|
# objectclasses parameter is a coma separated list in configuration
|
|
|
|
# split it to get a real list, and convert it to bytes
|
2019-02-07 20:16:39 +01:00
|
|
|
for o in re.split(r'\W+', self.get_param('objectclasses')):
|
2015-05-26 00:33:36 +02:00
|
|
|
self.objectclasses.append(self._str(o))
|
2015-06-14 20:55:23 +02:00
|
|
|
self.group_attrs = {}
|
2019-02-06 22:32:40 +01:00
|
|
|
self.group_attrs_keys = set([])
|
2015-06-14 20:55:23 +02:00
|
|
|
for param in config:
|
|
|
|
name, sep, group = param.partition('.')
|
|
|
|
if name == 'group_attr':
|
|
|
|
self.group_attrs[group] = self.get_param(param)
|
2019-02-06 22:32:40 +01:00
|
|
|
self.group_attrs_keys |= set(
|
2017-03-16 02:45:23 +01:00
|
|
|
self._extract_format_keys(self.get_param(param))
|
|
|
|
)
|
2015-05-26 00:33:36 +02:00
|
|
|
|
2015-05-22 20:05:24 +02:00
|
|
|
self.attrlist = []
|
|
|
|
for a in attrslist:
|
2015-05-26 00:33:36 +02:00
|
|
|
self.attrlist.append(self._str(a))
|
|
|
|
|
2015-07-29 08:15:54 +02:00
|
|
|
# exception handler (mainly to log something meaningful)
|
2015-06-28 19:54:19 +02:00
|
|
|
def _exception_handler(self, e):
|
2015-07-29 08:15:54 +02:00
|
|
|
""" Exception handling"""
|
2015-06-28 19:54:19 +02:00
|
|
|
et = type(e)
|
|
|
|
if et is ldap.OPERATIONS_ERROR:
|
2015-07-10 21:06:28 +02:00
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="cannot use starttls with ldaps://"
|
|
|
|
" uri (uri: " + self.uri + ")",
|
2015-07-10 21:06:28 +02:00
|
|
|
)
|
2015-06-28 19:54:19 +02:00
|
|
|
elif et is ldap.INVALID_CREDENTIALS:
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="Configuration error, wrong credentials,"
|
|
|
|
" unable to connect to ldap with '" + self.binddn + "'",
|
|
|
|
)
|
2015-06-28 19:54:19 +02:00
|
|
|
elif et is ldap.SERVER_DOWN:
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="Unable to contact ldap server '" +
|
|
|
|
self.uri +
|
|
|
|
"', check 'auth.ldap.uri'"
|
|
|
|
" and ssl/tls configuration",
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
elif et is ldap.FILTER_ERROR:
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="Bad search filter, check '" +
|
|
|
|
self.backend_name +
|
|
|
|
".*_filter_tmpl' params",
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
elif et is ldap.NO_SUCH_OBJECT:
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
2015-07-15 21:05:38 +02:00
|
|
|
msg="DN doesn't exist, check '" +
|
2015-07-11 22:25:21 +02:00
|
|
|
self.backend_name +
|
2015-07-15 21:05:38 +02:00
|
|
|
".userdn'or '" +
|
2015-07-11 22:25:21 +02:00
|
|
|
self.backend_name +
|
|
|
|
".groupdn'",
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
elif et is ldap.OBJECT_CLASS_VIOLATION:
|
|
|
|
info = e[0]['info']
|
|
|
|
desc = e[0]['desc']
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="Configuration error, " + desc + ", " + info,
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
elif et is ldap.INSUFFICIENT_ACCESS:
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="Access error on '" +
|
|
|
|
self.backend_name +
|
2015-07-29 08:15:54 +02:00
|
|
|
"' backend, please check your acls in backend " +
|
|
|
|
self.backend_name,
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
elif et is ldap.ALREADY_EXISTS:
|
|
|
|
desc = e[0]['desc']
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
|
|
|
msg="adding user failed, " + desc,
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
self._logger(
|
2015-07-11 22:25:21 +02:00
|
|
|
severity=logging.ERROR,
|
2015-07-29 08:15:54 +02:00
|
|
|
msg="unknow exception in backend " + self.backend_name,
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
2015-07-28 20:30:51 +02:00
|
|
|
raise
|
2015-06-28 19:54:19 +02:00
|
|
|
|
2017-03-16 02:45:23 +01:00
|
|
|
def _extract_format_keys(self, fmt_string):
|
|
|
|
"""Extract the keys of a format string
|
|
|
|
(the 'stuff' in '%(stuff)s'
|
|
|
|
"""
|
|
|
|
|
|
|
|
class AccessSaver:
|
|
|
|
def __init__(self):
|
|
|
|
self.keys = []
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
self.keys.append(key)
|
|
|
|
|
|
|
|
a = AccessSaver()
|
|
|
|
fmt_string % a
|
|
|
|
|
|
|
|
return a.keys
|
|
|
|
|
|
|
|
def _normalize_group_attrs(self, attrs):
|
|
|
|
"""Normalize the attributes used to set groups
|
|
|
|
If it's a list of one element, it just become this
|
|
|
|
element.
|
|
|
|
It raises an error if the attribute doesn't exist
|
|
|
|
or if it's multivaluated.
|
|
|
|
"""
|
|
|
|
for key in self.group_attrs_keys:
|
|
|
|
if key not in attrs:
|
|
|
|
raise MissingGroupAttr(key)
|
|
|
|
if type(attrs[key]) is list and len(attrs[key]) == 1:
|
|
|
|
attrs[key] = attrs[key][0]
|
|
|
|
if type(attrs[key]) is list and len(attrs[key]) != 1:
|
|
|
|
raise MultivaluedGroupAttr(key)
|
|
|
|
|
2015-05-28 09:45:10 +02:00
|
|
|
def _connect(self):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Initialize an ldap client"""
|
2015-05-28 09:45:10 +02:00
|
|
|
ldap_client = ldap.initialize(self.uri)
|
2015-07-05 22:01:09 +02:00
|
|
|
ldap.set_option(ldap.OPT_REFERRALS, 0)
|
|
|
|
ldap.set_option(ldap.OPT_TIMEOUT, self.timeout)
|
2015-05-28 09:45:10 +02:00
|
|
|
if self.starttls == 'on':
|
2015-07-05 22:01:09 +02:00
|
|
|
ldap.set_option(ldap.OPT_X_TLS_DEMAND, True)
|
2015-05-28 09:45:10 +02:00
|
|
|
else:
|
2015-07-05 22:01:09 +02:00
|
|
|
ldap.set_option(ldap.OPT_X_TLS_DEMAND, False)
|
2015-07-29 08:15:54 +02:00
|
|
|
# set the CA file if declared and if necessary
|
2015-05-28 09:45:10 +02:00
|
|
|
if self.ca and self.checkcert == 'on':
|
2015-07-29 08:15:54 +02:00
|
|
|
# check if the CA file actually exists
|
2015-07-05 22:01:09 +02:00
|
|
|
if os.path.isfile(self.ca):
|
|
|
|
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.ca)
|
|
|
|
else:
|
|
|
|
raise CaFileDontExist(self.ca)
|
2015-05-28 09:45:10 +02:00
|
|
|
if self.checkcert == 'off':
|
2015-07-05 22:01:09 +02:00
|
|
|
# this is dark magic
|
|
|
|
# remove any of these two lines and it doesn't work
|
|
|
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
2015-07-11 22:25:21 +02:00
|
|
|
ldap_client.set_option(
|
|
|
|
ldap.OPT_X_TLS_REQUIRE_CERT,
|
|
|
|
ldap.OPT_X_TLS_NEVER
|
|
|
|
)
|
2015-05-28 09:45:10 +02:00
|
|
|
else:
|
2015-07-05 22:01:09 +02:00
|
|
|
# this is even darker magic
|
2015-07-11 22:25:21 +02:00
|
|
|
ldap_client.set_option(
|
|
|
|
ldap.OPT_X_TLS_REQUIRE_CERT,
|
|
|
|
ldap.OPT_X_TLS_DEMAND
|
|
|
|
)
|
|
|
|
# it doesn't make sense to set it to never
|
|
|
|
# (== don't check certifate)
|
|
|
|
# but it only works with this option...
|
|
|
|
# ... and it checks the certificat
|
2015-07-05 22:01:09 +02:00
|
|
|
# (I've lost my sanity over this)
|
2015-07-11 22:25:21 +02:00
|
|
|
ldap.set_option(
|
|
|
|
ldap.OPT_X_TLS_REQUIRE_CERT,
|
|
|
|
ldap.OPT_X_TLS_NEVER
|
|
|
|
)
|
2015-06-06 22:23:21 +02:00
|
|
|
if self.starttls == 'on':
|
2015-05-28 09:45:10 +02:00
|
|
|
try:
|
|
|
|
ldap_client.start_tls_s()
|
2015-06-28 19:54:19 +02:00
|
|
|
except Exception as e:
|
|
|
|
self._exception_handler(e)
|
2015-05-28 09:45:10 +02:00
|
|
|
return ldap_client
|
|
|
|
|
|
|
|
def _bind(self):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""bind to the ldap with the technical account"""
|
2015-05-28 09:45:10 +02:00
|
|
|
ldap_client = self._connect()
|
|
|
|
try:
|
|
|
|
ldap_client.simple_bind_s(self.binddn, self.bindpassword)
|
2015-06-28 19:54:19 +02:00
|
|
|
except Exception as e:
|
2015-05-28 09:45:10 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-06-28 19:54:19 +02:00
|
|
|
self._exception_handler(e)
|
2015-05-28 09:45:10 +02:00
|
|
|
return ldap_client
|
|
|
|
|
|
|
|
def _search(self, searchfilter, attrs, basedn):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Generic search"""
|
2015-06-16 21:29:40 +02:00
|
|
|
if attrs == NO_ATTR:
|
|
|
|
attrlist = []
|
|
|
|
elif attrs == DISPLAYED_ATTRS:
|
|
|
|
# fix me later (to much attributes)
|
|
|
|
attrlist = self.attrlist
|
|
|
|
elif attrs == LISTED_ATTRS:
|
|
|
|
attrlist = self.attrlist
|
|
|
|
elif attrs == ALL_ATTRS:
|
|
|
|
attrlist = None
|
|
|
|
else:
|
|
|
|
attrlist = None
|
|
|
|
|
2015-07-29 08:15:54 +02:00
|
|
|
# bind and search the ldap
|
2015-05-28 09:45:10 +02:00
|
|
|
ldap_client = self._bind()
|
|
|
|
try:
|
2015-07-11 22:25:21 +02:00
|
|
|
r = ldap_client.search_s(
|
|
|
|
basedn,
|
|
|
|
ldap.SCOPE_SUBTREE,
|
|
|
|
searchfilter,
|
|
|
|
attrlist=attrlist
|
|
|
|
)
|
2015-06-28 19:54:19 +02:00
|
|
|
except Exception as e:
|
2015-05-28 09:45:10 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-06-28 19:54:19 +02:00
|
|
|
self._exception_handler(e)
|
2015-05-28 09:45:10 +02:00
|
|
|
|
|
|
|
ldap_client.unbind_s()
|
2015-07-28 23:27:02 +02:00
|
|
|
|
2015-07-29 08:15:54 +02:00
|
|
|
# python-ldap doesn't know utf-8,
|
|
|
|
# it treates everything as bytes.
|
|
|
|
# So it's necessary to reencode
|
|
|
|
# it's output in utf-8.
|
2015-07-28 23:27:02 +02:00
|
|
|
ret = []
|
|
|
|
for entry in r:
|
|
|
|
uni_dn = self._uni(entry[0])
|
|
|
|
uni_attrs = {}
|
|
|
|
for attr in entry[1]:
|
|
|
|
if type(entry[1][attr]) is list:
|
|
|
|
tmp = []
|
|
|
|
for value in entry[1][attr]:
|
|
|
|
tmp.append(self._uni(value))
|
|
|
|
else:
|
|
|
|
tmp = self._uni(entry[1][attr])
|
|
|
|
uni_attrs[self._uni(attr)] = tmp
|
|
|
|
ret.append((uni_dn, uni_attrs))
|
|
|
|
return ret
|
2015-05-28 09:45:10 +02:00
|
|
|
|
2015-06-16 21:29:40 +02:00
|
|
|
def _get_user(self, username, attrs=ALL_ATTRS):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Get a user from the ldap"""
|
2015-05-28 09:45:10 +02:00
|
|
|
|
2015-07-15 21:28:54 +02:00
|
|
|
username = ldap.filter.escape_filter_chars(username)
|
2015-05-28 09:45:10 +02:00
|
|
|
user_filter = self.user_filter_tmpl % {
|
2016-07-07 20:22:33 +02:00
|
|
|
'username': self._uni(username)
|
2015-05-28 09:45:10 +02:00
|
|
|
}
|
2016-07-07 20:22:33 +02:00
|
|
|
r = self._search(self._str(user_filter), attrs, self.userdn)
|
2015-05-28 09:45:10 +02:00
|
|
|
|
|
|
|
if len(r) == 0:
|
|
|
|
return None
|
|
|
|
|
2015-07-29 08:15:54 +02:00
|
|
|
# if NO_ATTR, only return the DN
|
2015-06-16 21:29:40 +02:00
|
|
|
if attrs == NO_ATTR:
|
2015-05-28 09:45:10 +02:00
|
|
|
dn_entry = r[0][0]
|
2015-07-29 08:15:54 +02:00
|
|
|
# in other cases, return everything (dn + attributes)
|
2015-06-16 21:29:40 +02:00
|
|
|
else:
|
|
|
|
dn_entry = r[0]
|
2015-05-28 09:45:10 +02:00
|
|
|
return dn_entry
|
2019-02-08 20:33:58 +01:00
|
|
|
|
2015-07-29 08:15:54 +02:00
|
|
|
# python-ldap talks in bytes,
|
|
|
|
# as the rest of ldapcherry talks in unicode utf-8:
|
|
|
|
# * everything passed to python-ldap must be converted to bytes
|
|
|
|
# * everything coming from python-ldap must be converted to unicode
|
2019-02-08 20:33:58 +01:00
|
|
|
if sys.version < '3':
|
|
|
|
def _str(self, s):
|
|
|
|
"""unicode -> bytes conversion"""
|
|
|
|
if s is None:
|
|
|
|
return None
|
|
|
|
return s.encode('utf-8')
|
2019-02-08 20:38:03 +01:00
|
|
|
|
2019-02-08 20:33:58 +01:00
|
|
|
def _uni(self, s):
|
|
|
|
"""bytes -> unicode conversion"""
|
|
|
|
if s is None:
|
|
|
|
return None
|
|
|
|
return s.decode('utf-8', 'ignore')
|
|
|
|
else:
|
|
|
|
def _str(self, s):
|
|
|
|
"""unicode -> bytes conversion"""
|
|
|
|
return s
|
|
|
|
|
|
|
|
def _uni(self, s):
|
|
|
|
"""bytes -> unicode conversion"""
|
|
|
|
if s is None:
|
|
|
|
return None
|
|
|
|
if type(s) is not str:
|
|
|
|
return s.decode('utf-8', 'ignore')
|
|
|
|
else:
|
|
|
|
return s
|
2015-05-20 17:13:18 +02:00
|
|
|
|
|
|
|
def auth(self, username, password):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Authentication of a user"""
|
2015-05-20 17:13:18 +02:00
|
|
|
|
2016-07-07 20:22:33 +02:00
|
|
|
binddn = self._get_user(self._str(username), NO_ATTR)
|
2015-07-11 22:25:21 +02:00
|
|
|
if binddn is not None:
|
2015-05-20 17:13:18 +02:00
|
|
|
ldap_client = self._connect()
|
|
|
|
try:
|
2016-07-07 20:22:33 +02:00
|
|
|
ldap_client.simple_bind_s(
|
|
|
|
self._str(binddn),
|
|
|
|
self._str(password)
|
|
|
|
)
|
2015-05-20 17:13:18 +02:00
|
|
|
except ldap.INVALID_CREDENTIALS:
|
|
|
|
ldap_client.unbind_s()
|
|
|
|
return False
|
|
|
|
ldap_client.unbind_s()
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
2016-06-17 00:10:02 +02:00
|
|
|
|
2016-06-16 21:49:48 +02:00
|
|
|
def attrs_pretreatment(self, attrs):
|
|
|
|
attrs_str = {}
|
|
|
|
for a in attrs:
|
|
|
|
attrs_str[self._str(a)] = self._str(attrs[a])
|
|
|
|
return attrs_str
|
2015-05-20 17:13:18 +02:00
|
|
|
|
2015-05-26 00:33:36 +02:00
|
|
|
def add_user(self, attrs):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""add a user"""
|
2015-05-26 00:33:36 +02:00
|
|
|
ldap_client = self._bind()
|
2015-07-29 08:15:54 +02:00
|
|
|
# encoding crap
|
2016-06-16 21:49:48 +02:00
|
|
|
attrs_str = self.attrs_pretreatment(attrs)
|
2016-06-17 00:10:02 +02:00
|
|
|
|
2015-05-26 00:33:36 +02:00
|
|
|
attrs_str['objectClass'] = self.objectclasses
|
2015-07-29 08:15:54 +02:00
|
|
|
# construct is DN
|
2015-07-11 22:25:21 +02:00
|
|
|
dn = \
|
2016-06-17 00:10:02 +02:00
|
|
|
self._str(self.dn_user_attr) + \
|
|
|
|
'=' + \
|
2016-07-31 12:21:26 +02:00
|
|
|
ldap.dn.escape_dn_chars(
|
|
|
|
self._str(attrs[self.dn_user_attr])
|
|
|
|
) + \
|
2016-06-17 00:10:02 +02:00
|
|
|
',' + \
|
2015-07-28 23:57:14 +02:00
|
|
|
self._str(self.userdn)
|
2015-07-29 08:15:54 +02:00
|
|
|
# gen the ldif fir add_s and add the user
|
2015-05-26 00:33:36 +02:00
|
|
|
ldif = modlist.addModlist(attrs_str)
|
|
|
|
try:
|
2015-07-10 21:06:28 +02:00
|
|
|
ldap_client.add_s(dn, ldif)
|
2015-07-31 22:43:51 +02:00
|
|
|
except ldap.ALREADY_EXISTS as e:
|
|
|
|
raise UserAlreadyExists(attrs[self.key], self.backend_name)
|
2015-06-28 19:54:19 +02:00
|
|
|
except Exception as e:
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-06-28 19:54:19 +02:00
|
|
|
self._exception_handler(e)
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-05-20 17:13:18 +02:00
|
|
|
|
|
|
|
def del_user(self, username):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""delete a user"""
|
2015-05-26 00:33:36 +02:00
|
|
|
ldap_client = self._bind()
|
2015-07-29 08:15:54 +02:00
|
|
|
# recover the user dn
|
2016-07-07 20:22:33 +02:00
|
|
|
dn = self._str(self._get_user(self._str(username), NO_ATTR))
|
2015-07-29 08:15:54 +02:00
|
|
|
# delete
|
2015-07-11 22:25:21 +02:00
|
|
|
if dn is not None:
|
2015-05-26 00:33:36 +02:00
|
|
|
ldap_client.delete_s(dn)
|
|
|
|
else:
|
2015-07-29 08:15:54 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-07-31 22:43:51 +02:00
|
|
|
raise UserDoesntExist(username, self.backend_name)
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-05-20 17:13:18 +02:00
|
|
|
|
2015-06-16 21:29:40 +02:00
|
|
|
def set_attrs(self, username, attrs):
|
2019-02-06 22:32:40 +01:00
|
|
|
""" set user attributes"""
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client = self._bind()
|
2016-07-07 20:22:33 +02:00
|
|
|
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
2016-07-31 11:39:28 +02:00
|
|
|
if tmp is None:
|
|
|
|
raise UserDoesntExist(username, self.backend_name)
|
2015-07-28 23:27:02 +02:00
|
|
|
dn = self._str(tmp[0])
|
2015-07-05 22:48:24 +02:00
|
|
|
old_attrs = tmp[1]
|
|
|
|
for attr in attrs:
|
2015-07-28 23:27:02 +02:00
|
|
|
bcontent = self._str(attrs[attr])
|
|
|
|
battr = self._str(attr)
|
|
|
|
new = {battr: bcontent}
|
2015-07-28 00:18:05 +02:00
|
|
|
# if attr is dn entry, use rename
|
|
|
|
if attr.lower() == self.dn_user_attr.lower():
|
|
|
|
ldap_client.rename_s(
|
|
|
|
dn,
|
2015-07-28 23:27:02 +02:00
|
|
|
ldap.dn.dn2str([[(battr, bcontent, 1)]])
|
2015-07-28 00:18:05 +02:00
|
|
|
)
|
2015-07-28 23:38:29 +02:00
|
|
|
dn = ldap.dn.dn2str(
|
|
|
|
[[(battr, bcontent, 1)]] + ldap.dn.str2dn(dn)[1:]
|
|
|
|
)
|
2015-06-14 20:55:23 +02:00
|
|
|
else:
|
2015-07-29 08:15:54 +02:00
|
|
|
# if attr is already set, replace the value
|
|
|
|
# (see dict old passed to modifyModlist)
|
2015-07-28 00:18:05 +02:00
|
|
|
if attr in old_attrs:
|
2015-07-28 23:27:02 +02:00
|
|
|
if type(old_attrs[attr]) is list:
|
|
|
|
tmp = []
|
|
|
|
for value in old_attrs[attr]:
|
|
|
|
tmp.append(self._str(value))
|
|
|
|
bold_value = tmp
|
|
|
|
else:
|
|
|
|
bold_value = self._str(old_attrs[attr])
|
|
|
|
old = {battr: bold_value}
|
2015-07-29 08:15:54 +02:00
|
|
|
# attribute is not set, just add it
|
2015-07-28 00:18:05 +02:00
|
|
|
else:
|
|
|
|
old = {}
|
|
|
|
ldif = modlist.modifyModlist(old, new)
|
|
|
|
try:
|
|
|
|
ldap_client.modify_s(dn, ldif)
|
|
|
|
except Exception as e:
|
|
|
|
ldap_client.unbind_s()
|
|
|
|
self._exception_handler(e)
|
2015-06-16 21:29:40 +02:00
|
|
|
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-05-24 17:32:03 +02:00
|
|
|
|
2015-06-16 21:58:44 +02:00
|
|
|
def add_to_groups(self, username, groups):
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client = self._bind()
|
2015-07-29 08:15:54 +02:00
|
|
|
# recover dn of the user and his attributes
|
2016-07-07 20:22:33 +02:00
|
|
|
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
2015-06-14 20:55:23 +02:00
|
|
|
dn = tmp[0]
|
2015-07-05 22:48:24 +02:00
|
|
|
attrs = tmp[1]
|
2015-06-14 20:55:23 +02:00
|
|
|
attrs['dn'] = dn
|
2017-03-16 02:45:23 +01:00
|
|
|
self._normalize_group_attrs(attrs)
|
2015-07-28 23:27:02 +02:00
|
|
|
dn = self._str(tmp[0])
|
2015-07-29 08:15:54 +02:00
|
|
|
# add user to all groups
|
2015-06-14 20:55:23 +02:00
|
|
|
for group in groups:
|
2015-06-16 23:56:12 +02:00
|
|
|
group = self._str(group)
|
2015-07-29 08:15:54 +02:00
|
|
|
# iterate on group membership attributes
|
2015-06-14 20:55:23 +02:00
|
|
|
for attr in self.group_attrs:
|
2015-07-29 08:15:54 +02:00
|
|
|
# fill the content template
|
2015-06-16 23:56:12 +02:00
|
|
|
content = self._str(self.group_attrs[attr] % attrs)
|
2015-06-18 20:38:10 +02:00
|
|
|
self._logger(
|
2015-07-10 21:06:28 +02:00
|
|
|
severity=logging.DEBUG,
|
2015-07-11 22:25:21 +02:00
|
|
|
msg="%(backend)s: adding user '%(user)s'"
|
|
|
|
" with dn '%(dn)s' to group '%(group)s' by"
|
|
|
|
" setting '%(attr)s' to '%(content)s'" % {
|
|
|
|
'user': username,
|
2015-07-28 23:27:02 +02:00
|
|
|
'dn': self._uni(dn),
|
2015-07-29 08:15:54 +02:00
|
|
|
'group': self._uni(group),
|
2015-07-11 22:25:21 +02:00
|
|
|
'attr': attr,
|
2015-07-28 23:27:02 +02:00
|
|
|
'content': self._uni(content),
|
2015-07-11 22:25:21 +02:00
|
|
|
'backend': self.backend_name
|
|
|
|
}
|
2015-06-18 20:38:10 +02:00
|
|
|
)
|
2015-07-10 21:06:28 +02:00
|
|
|
ldif = modlist.modifyModlist({}, {attr: content})
|
2015-06-16 23:56:12 +02:00
|
|
|
try:
|
|
|
|
ldap_client.modify_s(group, ldif)
|
2015-07-29 08:15:54 +02:00
|
|
|
# if already member, not a big deal, just log it and continue
|
2015-06-16 23:56:12 +02:00
|
|
|
except ldap.TYPE_OR_VALUE_EXISTS as e:
|
2015-06-18 20:38:10 +02:00
|
|
|
self._logger(
|
2015-07-10 21:06:28 +02:00
|
|
|
severity=logging.INFO,
|
2015-07-11 22:25:21 +02:00
|
|
|
msg="%(backend)s: user '%(user)s'"
|
|
|
|
" already member of group '%(group)s'"
|
2015-11-03 08:53:46 +01:00
|
|
|
" (attribute '%(attr)s')" % {
|
2015-07-11 22:25:21 +02:00
|
|
|
'user': username,
|
2015-07-29 08:15:54 +02:00
|
|
|
'group': self._uni(group),
|
2015-07-11 22:25:21 +02:00
|
|
|
'attr': attr,
|
|
|
|
'backend': self.backend_name
|
|
|
|
}
|
2015-06-18 20:38:10 +02:00
|
|
|
)
|
2015-07-15 21:05:38 +02:00
|
|
|
except ldap.NO_SUCH_OBJECT as e:
|
2015-07-31 22:43:51 +02:00
|
|
|
raise GroupDoesntExist(group, self.backend_name)
|
2015-06-28 19:54:19 +02:00
|
|
|
except Exception as e:
|
|
|
|
ldap_client.unbind_s()
|
|
|
|
self._exception_handler(e)
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-07-05 22:48:24 +02:00
|
|
|
|
2015-06-16 21:58:44 +02:00
|
|
|
def del_from_groups(self, username, groups):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Delete user from groups"""
|
|
|
|
# it follows the same logic than add_to_groups
|
|
|
|
# but with MOD_DELETE
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client = self._bind()
|
2016-07-07 20:22:33 +02:00
|
|
|
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
2016-08-01 19:57:51 +02:00
|
|
|
if tmp is None:
|
|
|
|
raise UserDoesntExist(username, self.backend_name)
|
2015-06-14 20:55:23 +02:00
|
|
|
dn = tmp[0]
|
2015-07-05 22:48:24 +02:00
|
|
|
attrs = tmp[1]
|
2015-06-14 20:55:23 +02:00
|
|
|
attrs['dn'] = dn
|
2017-03-16 02:45:23 +01:00
|
|
|
self._normalize_group_attrs(attrs)
|
2015-07-28 23:27:02 +02:00
|
|
|
dn = self._str(tmp[0])
|
2015-06-14 20:55:23 +02:00
|
|
|
for group in groups:
|
2015-06-16 23:56:12 +02:00
|
|
|
group = self._str(group)
|
2015-06-14 20:55:23 +02:00
|
|
|
for attr in self.group_attrs:
|
2015-06-16 23:56:12 +02:00
|
|
|
content = self._str(self.group_attrs[attr] % attrs)
|
2015-07-10 21:06:28 +02:00
|
|
|
ldif = [(ldap.MOD_DELETE, attr, content)]
|
2015-06-16 23:56:12 +02:00
|
|
|
try:
|
|
|
|
ldap_client.modify_s(group, ldif)
|
|
|
|
except ldap.NO_SUCH_ATTRIBUTE as e:
|
2015-06-28 19:54:19 +02:00
|
|
|
self._logger(
|
2015-07-10 21:06:28 +02:00
|
|
|
severity=logging.INFO,
|
2015-07-11 22:25:21 +02:00
|
|
|
msg="%(backend)s: user '%(user)s'"
|
|
|
|
" wasn't member of group '%(group)s'"
|
|
|
|
" (attribute '%(attr)s')" % {
|
|
|
|
'user': username,
|
2015-07-29 08:15:54 +02:00
|
|
|
'group': self._uni(group),
|
2015-07-11 22:25:21 +02:00
|
|
|
'attr': attr,
|
|
|
|
'backend': self.backend_name
|
|
|
|
}
|
2015-06-28 19:54:19 +02:00
|
|
|
)
|
|
|
|
except Exception as e:
|
|
|
|
ldap_client.unbind_s()
|
|
|
|
self._exception_handler(e)
|
2015-06-14 20:55:23 +02:00
|
|
|
ldap_client.unbind_s()
|
2015-05-25 19:52:54 +02:00
|
|
|
|
|
|
|
def search(self, searchstring):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Search users"""
|
|
|
|
# escape special char to avoid injection
|
2016-07-07 20:22:33 +02:00
|
|
|
searchstring = ldap.filter.escape_filter_chars(self._str(searchstring))
|
2015-07-29 08:15:54 +02:00
|
|
|
# fill the search string template
|
2015-05-25 19:52:54 +02:00
|
|
|
searchfilter = self.search_filter_tmpl % {
|
|
|
|
'searchstring': searchstring
|
|
|
|
}
|
2015-07-29 08:15:54 +02:00
|
|
|
|
|
|
|
ret = {}
|
|
|
|
# search an process the result a little
|
2015-06-16 21:29:40 +02:00
|
|
|
for u in self._search(searchfilter, DISPLAYED_ATTRS, self.userdn):
|
2015-05-31 18:40:35 +02:00
|
|
|
attrs = {}
|
|
|
|
attrs_tmp = u[1]
|
2015-06-06 22:23:21 +02:00
|
|
|
for attr in attrs_tmp:
|
2015-05-31 18:40:35 +02:00
|
|
|
value_tmp = attrs_tmp[attr]
|
|
|
|
if len(value_tmp) == 1:
|
2015-07-28 23:27:02 +02:00
|
|
|
attrs[attr] = value_tmp[0]
|
2015-05-31 18:40:35 +02:00
|
|
|
else:
|
2015-07-28 23:27:02 +02:00
|
|
|
attrs[attr] = value_tmp
|
2015-06-06 22:23:21 +02:00
|
|
|
|
2015-05-31 18:40:35 +02:00
|
|
|
if self.key in attrs:
|
|
|
|
ret[attrs[self.key]] = attrs
|
2015-06-06 22:23:21 +02:00
|
|
|
return ret
|
2015-05-25 19:52:54 +02:00
|
|
|
|
2015-05-26 22:50:42 +02:00
|
|
|
def get_user(self, username):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Gest a specific user"""
|
2015-05-26 22:50:42 +02:00
|
|
|
ret = {}
|
2016-07-07 20:22:33 +02:00
|
|
|
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
2015-07-15 21:05:38 +02:00
|
|
|
if tmp is None:
|
2015-07-31 22:43:51 +02:00
|
|
|
raise UserDoesntExist(username, self.backend_name)
|
2015-07-15 21:05:38 +02:00
|
|
|
attrs_tmp = tmp[1]
|
2015-06-06 22:23:21 +02:00
|
|
|
for attr in attrs_tmp:
|
2015-05-26 22:50:42 +02:00
|
|
|
value_tmp = attrs_tmp[attr]
|
|
|
|
if len(value_tmp) == 1:
|
2015-07-28 23:27:02 +02:00
|
|
|
ret[attr] = value_tmp[0]
|
2015-05-26 22:50:42 +02:00
|
|
|
else:
|
2015-07-28 23:27:02 +02:00
|
|
|
ret[attr] = value_tmp
|
2015-06-06 22:23:21 +02:00
|
|
|
return ret
|
2015-05-26 22:50:42 +02:00
|
|
|
|
2015-05-28 09:45:10 +02:00
|
|
|
def get_groups(self, username):
|
2015-07-29 08:15:54 +02:00
|
|
|
"""Get all groups of a user"""
|
2016-07-07 20:22:33 +02:00
|
|
|
username = ldap.filter.escape_filter_chars(self._str(username))
|
2015-06-16 21:29:40 +02:00
|
|
|
userdn = self._get_user(username, NO_ATTR)
|
2015-06-06 22:23:21 +02:00
|
|
|
|
2015-05-28 09:45:10 +02:00
|
|
|
searchfilter = self.group_filter_tmpl % {
|
|
|
|
'userdn': userdn,
|
2015-05-22 01:16:53 +02:00
|
|
|
'username': username
|
|
|
|
}
|
2015-05-20 17:13:18 +02:00
|
|
|
|
2015-06-16 21:29:40 +02:00
|
|
|
groups = self._search(searchfilter, NO_ATTR, self.groupdn)
|
2015-05-28 09:45:10 +02:00
|
|
|
ret = []
|
|
|
|
for entry in groups:
|
2015-07-29 08:15:54 +02:00
|
|
|
ret.append(entry[0])
|
2015-05-28 09:45:10 +02:00
|
|
|
return ret
|