#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# UCS Test Tools
"""Create may users in groups."""
#
# Copyright 2010-2020 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 sys
from optparse import OptionParser
import univention.debug
import univention.admin
import univention.admin.modules
import univention.admin.objects
import univention.admin.uldap
import ldap
from ldap.dn import escape_dn_chars
from random import choice
try:
	from typing import Iterator  # noqa F401
except ImportError:
	pass


def names(count):  # type: (int) -> Iterator[str]
	index = 0
	while index < count:
		yield 'nscd%04x' % index
		index += 1


class MassCreate(object):

	"""Mass create users and groups."""

	def __init__(self, count):
		# create LDAP connection
		self.access, self.position = univention.admin.uldap.getAdminConnection()

		# position of groups and users in LDAP dit
		self.gp = univention.admin.uldap.position(self.position.getDn())
		self.gp.setDn("cn=groups,%s" % self.position.getBase())

		self.up = univention.admin.uldap.position(self.position.getDn())
		self.up.setDn("cn=users,%s" % self.position.getBase())

		# dynamically get modules by name (univention.admin.handlers.$module.object)
		self.gg = univention.admin.modules.get("groups/group")
		univention.admin.modules.init(self.access, self.position, self.gg)

		self.uu = univention.admin.modules.get("users/user")
		univention.admin.modules.init(self.access, self.position, self.uu)

		self.count = count

		self.suffix = ''
		self.fast = True

	def group(self, name, in_group=None):
		"""Create group, which is itself member in_group."""
		g = self.gg.object(co=None, lo=self.access, position=self.gp)
		g.options = ['posix', 'samba']
		g.open()
		g.info['name'] = name
		g.info['sambaGroupType'] = "2"
		if in_group:
			g.info['memberOf'] = ["cn=%s,%s" % (escape_dn_chars(in_group), self.gp.getDn())]
		try:
			dn = g.create()
			print('Group "%s"' % (dn,))
		except univention.admin.uexceptions.groupNameAlreadyUsed as ex:
			dn = 'cn=%s,%s' % (escape_dn_chars(name), self.gp.getDn())
			print('Group "%s" exists: %s' % (dn, ex), file=sys.stderr)
		except univention.admin.uexceptions.objectExists as ex:
			dn = 'cn=%s,%s' % (escape_dn_chars(name), self.gp.getDn())
			print('Object "%s" exists: %s' % (dn, ex), file=sys.stderr)
		return dn

	def user(self, name, groups):
		"""Create user, which is itself member in groups."""
		gdn = ["cn=%s,%s" % (escape_dn_chars(group), self.gp.getDn()) for group in groups]
		u = self.uu.object(co=None, lo=self.access, position=self.up)
		u.open()
		u.info['username'] = name
		u.info['password'] = 'univention'
		u.info['firstname'] = name
		u.info['lastname'] = name
		u.info['groups'] = gdn
		# Per default, every user gets added to the default group 'Domain Users',
		# which gets very slow, since each time the group is loaded, than modified
		# by removing all previous members before adding them all back plus adding
		# the new user.
		u.info['primaryGroup'] = 'cn=Domain Users,%s' % self.gp.getDn()
		u.info['unixhome'] = '/home/%s' % name
		try:
			dn = u.create()
			print('User "%s"' % (dn,))
		except univention.admin.uexceptions.uidAlreadyUsed as ex:
			dn = 'uid=%s,%s' % (escape_dn_chars(name), self.up.getDn())
			print('User "%s" exists: %s' % (dn, ex), file=sys.stderr)
		except univention.admin.uexceptions.objectExists as ex:
			dn = 'uid=%s,%s' % (escape_dn_chars(name), self.up.getDn())
			print('Object "%s" exists: %s' % (dn, ex), file=sys.stderr)
		return dn

	def group_members(self, name, uniqueMember=None, memberUid=None):
		"""Set members of group."""
		dn = "cn=%s,%s" % (escape_dn_chars(name), self.gp.getDn())
		ml = []
		if uniqueMember:
			uniqueMember = [_.encode('utf8') for _ in uniqueMember]
			ml.append((ldap.MOD_ADD, 'uniqueMember', uniqueMember))
		else:
			ml.append((ldap.MOD_DELETE, 'uniqueMember', None))
		if uniqueMember:
			memberUid = [_.encode('utf8') for _ in memberUid]
			ml.append((ldap.MOD_ADD, 'memberUid', memberUid))
		else:
			ml.append((ldap.MOD_DELETE, 'memberUid', memberUid))
		if ml:
			try:
				try:
					res = self.access.lo.lo.modify_s(dn, ml)
					assert res
				except ldap.TYPE_OR_VALUE_EXISTS as ex:
					print('XXX %s' % (ex,), file=sys.stderr)
					ml = [(ldap.MOD_REPLACE, attr, val) for (_op, attr, val) in ml]
					res = self.access.lo.lo.modify_s(dn, ml)
					assert res
			except ldap.NO_SUCH_ATTRIBUTE as ex:
				print('Group "%s" error: %s' % (dn, ex), file=sys.stderr)
		return dn

	def create(self, groups):
		"""Create hierarchy of groups and users."""
		for group in groups:
			self.group(group)
		self.group('nscd_all')

		uniqueMember = []
		memberUid = []

		for name in names(self.count):
			if name.endswith('000'):
				self.group(name[:-3], 'nscd_all')
			if name.endswith('00'):
				self.group(name[:-2], name[:-3])
			if name.endswith('0'):
				self.group(name[:-1], name[:-2])

			user_name = name
			if self.suffix:
				user_name += choice(self.suffix)
			dn = self.user(user_name, [name[:-2], name[:-1]] + groups)
			uniqueMember.append(dn)
			memberUid.append(name)

			if self.fast and name.endswith('00'):
				# temporarily clear group
				for group in groups:
					self.group_members(group)

		# finally refill group with all users
		for group in groups:
			self.group_members(group, uniqueMember=uniqueMember, memberUid=memberUid)


def main():
	"""Create users."""
	description = 'Create may users and groups.'
	parser = OptionParser(description=description)
	parser.add_option(
		'-g', '--group-only',
		dest='groups', action='append', default=['Domain Users'],
		help='Add all users these groups [%default]')
	parser.add_option(
		'-n', '--number',
		dest='count', action='store', type='int', default=1 << 15,
		help='Number of users to create [%default]')
	parser.add_option(
		'-s', '--suffix',
		dest='suffix', action='store', default='',
		help='Specify character set for random suffix')
	parser.add_option(
		'-f', '--fast',
		dest='fast', action='store_false', default=True,
		help='Disable intermediate clean groups to speed up user creation')
	options, args = parser.parse_args()

	encoding = sys.getfilesystemencoding()

	# setup Logger
	univention.debug.init('/dev/stderr', univention.debug.ADMIN, univention.debug.ALL)
	# Update module list (needed since UCS 3.0)
	univention.admin.modules.update()

	# Create users and groups
	generator = MassCreate(options.count)
	generator.suffix = options.suffix.decode(encoding)
	generator.fast = options.fast
	groups = [group.decode(encoding) for group in options.groups]
	generator.create(groups)


if __name__ == '__main__':
	main()
