2016-08-27 09:42:53 +02:00
|
|
|
# -*- coding: UTF-8 -*-
|
|
|
|
|
|
|
|
# This code is part of Ansible, but is an independent component.
|
|
|
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
|
|
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
|
|
|
# still belong to the author of the module, and may assign their own license
|
|
|
|
# to the complete work.
|
|
|
|
#
|
|
|
|
# Copyright (c) 2016, Adfinis SyGroup AG
|
|
|
|
# Tobias Rueetschi <tobias.ruetschi@adfinis-sygroup.ch>
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
# are permitted provided that the following conditions are met:
|
|
|
|
#
|
|
|
|
# * Redistributions of source code must retain the above copyright
|
|
|
|
# notice, this list of conditions and the following disclaimer.
|
|
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
|
|
# and/or other materials provided with the distribution.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
|
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
"""Univention Corporate Server (UCS) access module.
|
|
|
|
|
|
|
|
Provides the following functions for working with an UCS server.
|
|
|
|
|
|
|
|
- ldap_search(filter, base=None, attr=None)
|
|
|
|
Search the LDAP via Univention's LDAP wrapper (ULDAP)
|
|
|
|
|
|
|
|
- config_registry()
|
|
|
|
Return the UCR registry object
|
|
|
|
|
|
|
|
- base_dn()
|
|
|
|
Return the configured Base DN according to the UCR
|
|
|
|
|
|
|
|
- uldap()
|
|
|
|
Return a handle to the ULDAP LDAP wrapper
|
|
|
|
|
|
|
|
- umc_module_for_add(module, container_dn, superordinate=None)
|
|
|
|
Return a UMC module for creating a new object of the given type
|
|
|
|
|
|
|
|
- umc_module_for_edit(module, object_dn, superordinate=None)
|
|
|
|
Return a UMC module for editing an existing object of the given type
|
|
|
|
|
|
|
|
|
|
|
|
Any other module is not part of the "official" API and may change at any time.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'ldap_search',
|
|
|
|
'config_registry',
|
|
|
|
'base_dn',
|
|
|
|
'uldap',
|
|
|
|
'umc_module_for_add',
|
|
|
|
'umc_module_for_edit',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
_singletons = {}
|
|
|
|
|
|
|
|
|
|
|
|
def ldap_module():
|
|
|
|
import ldap as orig_ldap
|
|
|
|
return orig_ldap
|
|
|
|
|
|
|
|
|
|
|
|
def _singleton(name, constructor):
|
|
|
|
if name in _singletons:
|
|
|
|
return _singletons[name]
|
|
|
|
_singletons[name] = constructor()
|
|
|
|
return _singletons[name]
|
|
|
|
|
|
|
|
|
|
|
|
def config_registry():
|
|
|
|
|
|
|
|
def construct():
|
|
|
|
import univention.config_registry
|
|
|
|
ucr = univention.config_registry.ConfigRegistry()
|
|
|
|
ucr.load()
|
|
|
|
return ucr
|
|
|
|
|
|
|
|
return _singleton('config_registry', construct)
|
|
|
|
|
|
|
|
|
|
|
|
def base_dn():
|
|
|
|
return config_registry()['ldap/base']
|
|
|
|
|
|
|
|
|
|
|
|
def uldap():
|
|
|
|
"Return a configured univention uldap object"
|
|
|
|
|
|
|
|
def construct():
|
|
|
|
try:
|
|
|
|
secret_file = open('/etc/ldap.secret', 'r')
|
2018-01-10 21:03:25 +01:00
|
|
|
bind_dn = 'cn=admin,{0}'.format(base_dn())
|
2016-08-27 09:42:53 +02:00
|
|
|
except IOError: # pragma: no cover
|
|
|
|
secret_file = open('/etc/machine.secret', 'r')
|
2017-06-02 13:14:11 +02:00
|
|
|
bind_dn = config_registry()["ldap/hostdn"]
|
|
|
|
pwd_line = secret_file.readline()
|
|
|
|
pwd = re.sub('\n', '', pwd_line)
|
2016-08-27 09:42:53 +02:00
|
|
|
|
|
|
|
import univention.admin.uldap
|
|
|
|
return univention.admin.uldap.access(
|
2017-06-02 13:14:11 +02:00
|
|
|
host=config_registry()['ldap/master'],
|
|
|
|
base=base_dn(),
|
|
|
|
binddn=bind_dn,
|
|
|
|
bindpw=pwd,
|
|
|
|
start_tls=1,
|
2016-08-27 09:42:53 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return _singleton('uldap', construct)
|
|
|
|
|
|
|
|
|
|
|
|
def config():
|
|
|
|
def construct():
|
|
|
|
import univention.admin.config
|
|
|
|
return univention.admin.config.config()
|
|
|
|
return _singleton('config', construct)
|
|
|
|
|
|
|
|
|
|
|
|
def init_modules():
|
|
|
|
def construct():
|
|
|
|
import univention.admin.modules
|
|
|
|
univention.admin.modules.update()
|
|
|
|
return True
|
|
|
|
return _singleton('modules_initialized', construct)
|
|
|
|
|
|
|
|
|
|
|
|
def position_base_dn():
|
|
|
|
def construct():
|
|
|
|
import univention.admin.uldap
|
|
|
|
return univention.admin.uldap.position(base_dn())
|
|
|
|
return _singleton('position_base_dn', construct)
|
|
|
|
|
|
|
|
|
|
|
|
def ldap_dn_tree_parent(dn, count=1):
|
|
|
|
dn_array = dn.split(',')
|
|
|
|
dn_array[0:count] = []
|
|
|
|
return ','.join(dn_array)
|
|
|
|
|
|
|
|
|
|
|
|
def ldap_search(filter, base=None, attr=None):
|
|
|
|
"""Replaces uldaps search and uses a generator.
|
|
|
|
!! Arguments are not the same."""
|
|
|
|
|
|
|
|
if base is None:
|
|
|
|
base = base_dn()
|
|
|
|
msgid = uldap().lo.lo.search(
|
|
|
|
base,
|
|
|
|
ldap_module().SCOPE_SUBTREE,
|
|
|
|
filterstr=filter,
|
|
|
|
attrlist=attr
|
|
|
|
)
|
|
|
|
# I used to have a try: finally: here but there seems to be a bug in python
|
|
|
|
# which swallows the KeyboardInterrupt
|
|
|
|
# The abandon now doesn't make too much sense
|
|
|
|
while True:
|
|
|
|
result_type, result_data = uldap().lo.lo.result(msgid, all=0)
|
|
|
|
if not result_data:
|
|
|
|
break
|
|
|
|
if result_type is ldap_module().RES_SEARCH_RESULT: # pragma: no cover
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if result_type is ldap_module().RES_SEARCH_ENTRY:
|
|
|
|
for res in result_data:
|
|
|
|
yield res
|
|
|
|
uldap().lo.lo.abandon(msgid)
|
|
|
|
|
|
|
|
|
|
|
|
def module_by_name(module_name_):
|
|
|
|
"""Returns an initialized UMC module, identified by the given name.
|
|
|
|
|
|
|
|
The module is a module specification according to the udm commandline.
|
|
|
|
Example values are:
|
|
|
|
* users/user
|
|
|
|
* shares/share
|
|
|
|
* groups/group
|
|
|
|
|
|
|
|
If the module does not exist, a KeyError is raised.
|
|
|
|
|
|
|
|
The modules are cached, so they won't be re-initialized
|
|
|
|
in subsequent calls.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def construct():
|
|
|
|
import univention.admin.modules
|
|
|
|
init_modules()
|
|
|
|
module = univention.admin.modules.get(module_name_)
|
|
|
|
univention.admin.modules.init(uldap(), position_base_dn(), module)
|
|
|
|
return module
|
|
|
|
|
|
|
|
return _singleton('module/%s' % module_name_, construct)
|
|
|
|
|
|
|
|
|
|
|
|
def get_umc_admin_objects():
|
|
|
|
"""Convenience accessor for getting univention.admin.objects.
|
|
|
|
|
|
|
|
This implements delayed importing, so the univention.* modules
|
|
|
|
are not loaded until this function is called.
|
|
|
|
"""
|
|
|
|
import univention.admin
|
|
|
|
return univention.admin.objects
|
|
|
|
|
|
|
|
|
|
|
|
def umc_module_for_add(module, container_dn, superordinate=None):
|
|
|
|
"""Returns an UMC module object prepared for creating a new entry.
|
|
|
|
|
|
|
|
The module is a module specification according to the udm commandline.
|
|
|
|
Example values are:
|
|
|
|
* users/user
|
|
|
|
* shares/share
|
|
|
|
* groups/group
|
|
|
|
|
|
|
|
The container_dn MUST be the dn of the container (not of the object to
|
|
|
|
be created itself!).
|
|
|
|
"""
|
|
|
|
mod = module_by_name(module)
|
|
|
|
|
|
|
|
position = position_base_dn()
|
|
|
|
position.setDn(container_dn)
|
|
|
|
|
|
|
|
# config, ldap objects from common module
|
|
|
|
obj = mod.object(config(), uldap(), position, superordinate=superordinate)
|
|
|
|
obj.open()
|
|
|
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
|
|
def umc_module_for_edit(module, object_dn, superordinate=None):
|
|
|
|
"""Returns an UMC module object prepared for editing an existing entry.
|
|
|
|
|
|
|
|
The module is a module specification according to the udm commandline.
|
|
|
|
Example values are:
|
|
|
|
* users/user
|
|
|
|
* shares/share
|
|
|
|
* groups/group
|
|
|
|
|
|
|
|
The object_dn MUST be the dn of the object itself, not the container!
|
|
|
|
"""
|
|
|
|
mod = module_by_name(module)
|
|
|
|
|
|
|
|
objects = get_umc_admin_objects()
|
|
|
|
|
|
|
|
position = position_base_dn()
|
|
|
|
position.setDn(ldap_dn_tree_parent(object_dn))
|
|
|
|
|
|
|
|
obj = objects.get(
|
|
|
|
mod,
|
|
|
|
config(),
|
|
|
|
uldap(),
|
|
|
|
position=position,
|
|
|
|
superordinate=superordinate,
|
|
|
|
dn=object_dn
|
|
|
|
)
|
|
|
|
obj.open()
|
|
|
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
|
|
def create_containers_and_parents(container_dn):
|
|
|
|
"""Create a container and if needed the parents containers"""
|
|
|
|
import univention.admin.uexceptions as uexcp
|
2017-11-13 17:51:18 +01:00
|
|
|
if not container_dn.startswith("cn="):
|
|
|
|
raise AssertionError()
|
2016-08-27 09:42:53 +02:00
|
|
|
try:
|
|
|
|
parent = ldap_dn_tree_parent(container_dn)
|
|
|
|
obj = umc_module_for_add(
|
|
|
|
'container/cn',
|
|
|
|
parent
|
|
|
|
)
|
|
|
|
obj['name'] = container_dn.split(',')[0].split('=')[1]
|
|
|
|
obj['description'] = "container created by import"
|
|
|
|
except uexcp.ldapError:
|
|
|
|
create_containers_and_parents(parent)
|
|
|
|
obj = umc_module_for_add(
|
|
|
|
'container/cn',
|
|
|
|
parent
|
|
|
|
)
|
|
|
|
obj['name'] = container_dn.split(',')[0].split('=')[1]
|
|
|
|
obj['description'] = "container created by import"
|