# -*- coding: utf-8 -*-
#
# Copyright 2013-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.
from __future__ import print_function
import subprocess
import sys
from typing import Iterable, Mapping, Optional, Sequence, Union  # noqa: F401
from univention.config_registry import ConfigRegistry
from univention.testing.strings import random_name, random_version
from univention.testing.utils import get_ldap_connection, fail
VALID_EXTENSION_TYPES = ('hook', 'syntax', 'module')
[docs]def get_package_name():
	# type: () -> str
	""" returns a valid package name """
	return random_name() 
[docs]def get_package_version():
	# type: () -> str
	return random_version() 
[docs]def get_extension_name(extension_type):
	# type: (str) -> str
	"""
	Returns a valid extension name for the given extension type.
	>>> get_extension_name('hook')
	'jcvuardfqx'
	>>> get_extension_name('syntax')
	'hwvkm29tde'
	>>> get_extension_name('module')
	'ucstest/r3jkljngcp'
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if extension_type == 'module':
		return 'ucstest/%s' % random_name()
	else:
		return random_name() 
[docs]def get_extension_filename(extension_type, extension_name):
	# type: (str, str) -> str
	assert(extension_type in VALID_EXTENSION_TYPES)
	return '%s.py' % extension_name 
[docs]def call_cmd(cmd, fail_on_error=True):
	# type: (Sequence[str], bool) -> int
	"""
	Calls the given cmd (list of strings).
	"""
	print('CMD: %r' % cmd)
	sys.stdout.flush()
	exitcode = subprocess.call(cmd)
	if fail_on_error and exitcode:
		fail('EXITCODE of script %s: %r' % (cmd[0], exitcode))
	return exitcode 
[docs]def call_join_script(name, fail_on_error=True):
	# type: (str, bool) -> int
	"""
	Calls the given join script (e.g. name='66foobar.inst').
	If fail is true, then the function fail() is called if the exitcode is not zero.
	"""
	ucr = ConfigRegistry()
	ucr.load()
	return call_cmd(['/usr/lib/univention-install/%s' % name, '--binddn', ucr.get('tests/domainadmin/account'), '--bindpwdfile', ucr.get('tests/domainadmin/pwdfile')], fail_on_error=fail_on_error) 
[docs]def call_unjoin_script(name, fail_on_error=True):
	# type: (str, bool) -> int
	"""
	Calls the given unjoin script (e.g. name='66foobar-uninstall.uinst').
	If fail is true, then the function fail() is called if the exitcode is not zero.
	"""
	ucr = ConfigRegistry()
	ucr.load()
	return call_cmd(['/usr/lib/univention-uninstall/%s' % name, '--binddn', ucr.get('tests/domainadmin/account'), '--bindpwdfile', ucr.get('tests/domainadmin/pwdfile')], fail_on_error=fail_on_error) 
[docs]def get_syntax_buffer(name=None, identifier=None):
	# type: (Optional[str], Optional[str]) -> str
	"""
	Returns a UDM syntax with given name (e.g. 'MySimpleHook'). If name is omitted,
	a randomly generated name is used.
	"""
	if name is None:
		name = random_name()
	if identifier is None:
		identifier = name
	return '''# UCS-TEST SYNTAX %(syntax_identifier)s
class %(syntax_name)s(simple):
		regex = re.compile('^ucstest-[0-9A-Za-z]+$')
		error_message = 'Wrong value given for ucs-test-syntax!'
''' % {'syntax_name': name, 'syntax_identifier': identifier} 
[docs]def get_hook_buffer(name=None, identifier=None):
	# type: (Optional[str], Optional[str]) -> str
	"""
	Returns a UDM hook with given name (e.g. 'MySimpleHook'). If name is omitted,
	a randomly generated name is used.
	"""
	if name is None:
		name = random_name()
	if identifier is None:
		identifier = name
	return '''# UCS-TEST HOOK %(hook_identifier)s
from univention.admin.hook import simpleHook
class %(hook_name)s(simpleHook):
	type = 'SetDescriptionValue'
	def hook_ldap_pre_modify(self, obj):
		""" Set description consisting of username, lastname """
		obj['description'] = 'USERNAME=%%(username)s  LASTNAME=%%(lastname)s' %% obj.info
''' % {'hook_name': name, 'hook_identifier': identifier} 
[docs]def get_module_buffer(name=None, identifier=None):
	# type: (Optional[str], Optional[str]) -> str
	"""
	Returns a UDM module with given name (e.g. 'testing/mytest'). If name is omitted,
	a randomly generated name is used ('ucstest/%(randomstring)s').
	"""
	if name is None:
		name = 'ucstest/%s' % (random_name(), )
	assert('/' in name)
	if identifier is None:
		identifier = name
	return '''# this UDM module has been created by ucs-test
from univention.admin.layout import Tab, Group
import univention.admin.filter
import univention.admin.handlers
import univention.admin.allocators
import univention.admin.localization
import univention.debug as ud
translation = univention.admin.localization.translation('univention.admin.handlers.ucstest')
_ = translation.translate
module = '%(module_udmname)s'
operations = ['add','edit','remove','search']
childs = 0
short_description = _('UCS-TEST MODULE %(module_identifier)s')
long_description = ''
options = {
	'default': univention.admin.option(
		short_description=short_description,
		default=True,
		objectClasses=['top', 'automountMap'],
	)
}
property_descriptions = {
	'name': univention.admin.property(
		short_description=_('Name'),
		long_description='',
		syntax=univention.admin.syntax.string,
		include_in_default_search=True,
		required=True,
		may_change=False,
		identifies=True
	),
}
layout = [
	Tab( _( 'General' ), _( 'Basic settings' ), layout = [
		Group( _( 'General' ), layout = [[ "name" ]] ),
	] )
]
mapping = univention.admin.mapping.mapping()
mapping.register('name', 'ou', None, univention.admin.mapping.ListToString)
class object(univention.admin.handlers.simpleLdap):
	module = module
lookup = object.lookup
identify = object.identify
''' % {'module_udmname': name, 'module_identifier': identifier} 
[docs]def get_extension_buffer(extension_type, name=None, identifier=None):
	# type: (str, Optional[str], Optional[str]) -> str
	"""
	Get UDM extension of specified type with specified name.
	In case the name is omitted, a random name will be used.
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	return {
		'hook': get_hook_buffer,
		'syntax': get_syntax_buffer,
		'module': get_module_buffer,
	}[extension_type](name, identifier) 
[docs]def get_postinst_script_buffer(extension_type, filename, app_id=None, version_start=None, version_end=None, options=None):
	# type: (str, str, Optional[str], Optional[str], Optional[str], Mapping[str, Union[str, Iterable[str]]]) -> str
	"""
	Returns a postinst script that registers the given file as UDM extension with extension type ('hook', 'syntax' or 'module').
	Optionally UNIVENTION_APP_ID, UCS version start and UCS version end may be specified.
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if not app_id:
		app_id = ''
	else:
		app_id = 'export UNIVENTION_APP_IDENTIFIER="%s"' % app_id
	if not version_start:
		version_start = ''
	else:
		version_start = '--ucsversionstart %s' % version_start
	if not version_end:
		version_end = ''
	else:
		version_end = '--ucsversionend %s' % version_end
	other_options = ''
	if options:
		for key in options:
			if isinstance(options[key], str):
				other_options += ' --%s %s' % (key, options[key])
			else:
				other_options += ' --%s ' % (key,) + ' --%s '.join(options[key])
	return '''#!/bin/sh
set -e
#DEBHELPER#
%(app_id)s
. /usr/share/univention-lib/ldap.sh
ucs_registerLDAPExtension "$@" --udm_%(extension_type)s %(filename)s %(version_start)s %(version_end)s %(other_options)s
exit 0
''' % {
		'filename': filename,
		'extension_type': extension_type,
		'app_id': app_id,
		'version_start': version_start,
		'version_end': version_end,
		'other_options': other_options
	} 
[docs]def get_postrm_script_buffer(extension_type, extension_name, package_name):
	# type: (str, str, str) -> str
	"""
	Returns an postrm script that deregisters the given UDM extension. The type of the extension
	has to be specified ('hook', 'syntax' or 'module').
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if extension_type == 'module':
		assert('/' in extension_name)
	return'''#!/bin/sh
set -e
#DEBHELPER#
. /usr/share/univention-lib/ldap.sh
ucs_unregisterLDAPExtension "$@" --udm_%(extension_type)s %(extension_name)s
exit 0
''' % {'extension_name': extension_name, 'extension_type': extension_type} 
[docs]def get_join_script_buffer(extension_type, filename, app_id=None, joinscript_version=1, version_start=None, version_end=None, options=None):
	# type: (str, str, Optional[str], int, Optional[str], Optional[str], Mapping[str, Union[str, Iterable[str]]]) -> str
	"""
	Returns a join script that registers the given file as UDM extension with extension type ('hook', 'syntax' or 'module').
	Optionally a joinscript version, UNIVENTION_APP_ID, UCS version start and UCS version end may be specified.
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if not app_id:
		app_id = ''
	else:
		app_id = 'export UNIVENTION_APP_IDENTIFIER="%s"' % app_id
	if not version_start:
		version_start = ''
	else:
		version_start = '--ucsversionstart %s' % version_start
	if not version_end:
		version_end = ''
	else:
		version_end = '--ucsversionend %s' % version_end
	other_options = ''
	if options:
		for key in options:
			if isinstance(options[key], str):
				other_options += ' --%s %s' % (key, options[key])
			else:
				other_options += ' --%s ' % (key,) + (' --%s ' % (key,)).join(options[key])
	return '''#!/bin/sh
VERSION=%(joinscript_version)s
set -e
. /usr/share/univention-join/joinscripthelper.lib
joinscript_init
%(app_id)s
. /usr/share/univention-lib/ldap.sh
ucs_registerLDAPExtension "$@" --udm_%(extension_type)s %(filename)s %(version_start)s %(version_end)s %(other_options)s
joinscript_save_current_version
exit 0
''' % {
		'filename': filename,
		'joinscript_version': joinscript_version,
		'extension_type': extension_type,
		'app_id': app_id,
		'version_start': version_start,
		'version_end': version_end,
		'other_options': other_options
	} 
[docs]def get_unjoin_script_buffer(extension_type, extension_name, package_name):
	# type: (str, str, str) -> str
	"""
	Returns an unjoin script that deregisters the given UDM extension. The type of the extension
	has to be specified ('hook', 'syntax' or 'module').
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if extension_type == 'module':
		assert('/' in extension_name)
	return'''#!/bin/sh
VERSION=1
set -e
. /usr/share/univention-join/joinscripthelper.lib
joinscript_init
. /usr/share/univention-lib/ldap.sh
ucs_unregisterLDAPExtension "$@" --udm_%(extension_type)s %(extension_name)s
joinscript_remove_script_from_status_file %(package_name)s
exit 0
''' % {'package_name': package_name, 'extension_name': extension_name, 'extension_type': extension_type} 
[docs]def get_absolute_extension_filename(extension_type, filename):
	# type: (str, str) -> str
	"""
	Returns the absolute path to an extentension of the given type and filename.
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if extension_type == 'module':
		assert('/' in filename)
	return '/usr/lib/python2.7/dist-packages%s' % ({
		'hook': '/univention/admin/hooks.d/%s',
		'syntax': '/univention/admin/syntax.d/%s',
		'module': '/univention/admin/handlers/%s',
	}[extension_type] % filename) 
[docs]def get_dn_of_extension_by_name(extension_type, name):
	# type: (str, str) -> str
	"""
	Returns a list of DNs of UDM extension objects with given type an name, or [] if no object has been found.
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	if extension_type == 'module':
		assert('/' in name)
	searchfilter = {
		'hook': '(&(objectClass=univentionUDMHook)(cn=%s))' % name,
		'syntax': '(&(objectClass=univentionUDMSyntax)(cn=%s))' % name,
		'module': '(&(objectClass=univentionUDMModule)(cn=%s))' % name,
	}[extension_type]
	return get_ldap_connection().searchDn(filter=searchfilter) 
[docs]def remove_extension_by_name(extension_type, extension_name, fail_on_error=True):
	# type: (str, str, bool) -> None
	"""
	Remove all extensions of given type and name from LDAP.
	"""
	assert(extension_type in VALID_EXTENSION_TYPES)
	for dn in get_dn_of_extension_by_name(extension_type, extension_name):
		cmd = ['/usr/sbin/udm-test', 'settings/udm_%s' % extension_type, 'remove', '--dn', dn]
		print('CMD: %r' % cmd)
		sys.stdout.flush()
		if subprocess.call(cmd):
			if fail_on_error:
				fail('Failed to remove %s' % dn)
			else:
				print('ERROR: failed to remove %s' % dn)