#!/usr/share/ucs-test/runner python
# coding=utf-8
## desc: ucs-school-ldap-acls
## roles: [domaincontroller_master]
## versions:
##  4.0-0: skip
## tags: [apptest, ucsschool]
## exposure: dangerous
## packages: [ucs-school-ldap-acls-master]

from essential.acl import run_commands
from essential.computerroom import Computers
from essential.schoolroom import ComputerRoom
from univention.uldap import getMachineConnection
import univention.testing.ucr as ucr_test
import univention.testing.ucsschool as utu


class FailAcl(Exception):
	pass


class FailCmd(Exception):
	pass


class Attributes(object):
	base = [
		'entry',
		'children',
		'dc',
		'univentionObjectType',
		'krb5RealmName',
		'nisDomain',
		'associatedDomain',
		'univentionPolicyReference',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'objectClass',
		'msGPOLink',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	container = ['children', 'entry', ]
	room = [
		'entry',
		'children',
		'sambaGroupType',
		'cn',
		'objectClass',
		'univentionObjectType',
		'gidNumber',
		'sambaSID',
		'univentionGroupType',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
		'hasSubordinates',
		'entryDN',
		'subschemaSubentry',
	]
	tea_stu_groups = [
		'objectClass',
		'univentionObjectType',
		'cn',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	tea_stu_groups_tree = [
		'entry',
		'children',
		'objectClass',
		'univentionObjectType',
		'cn',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	tea_stu = [
		'sambaGroupType',
		'cn',
		'description',
		'objectClass',
		'memberUid',
		'univentionObjectType',
		'gidNumber',
		'sambaSID',
		'uniqueMember',
		'univentionGroupType',
	]
	gid_temp = ['children', 'entry', 'univentionLastUsedValue']
	temp_tree = [
		'children',
		'entry',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
		'entryDN',
		'subschemaSubentry',
		'hasSubordinates'
	]
	temp = [
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
		'entryDN',
		'subschemaSubentry',
		'hasSubordinates'
	]
	ou = [
		'entry',
		'children',
		'ou',
		'displayName',
		'univentionObjectType',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'ucsschoolHomeShareFileServer',
		'ucsschoolClassShareFileServer',
		'univentionPolicyReference',
		'objectClass',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	global_containers = [
		'objectClass',
		'univentionObjectType',
		'description',
		'cn',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	global_containers_tree = [
		'entry',
		'children',
		'objectClass',
		'univentionObjectType',
		'description',
		'cn',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	computer = ['macAddress', 'sambaNTPassword']
	user = [
		'krb5KeyVersionNumber',
		'krb5KDCFlags',
		'krb5Key',
		'krb5PasswordEnd',
		'sambaAcctFlags',
		'sambaPwdLastSet',
		'sambaLMPassword',
		'sambaNTPassword',
		'shadowLastChange',
		'shadowMax',
		'userPassword',
		'pwhistory',
		'sambaPwdCanChange',
		'sambaPwdMustChange',
		'sambaPasswordHistory',
		'sambaBadPasswordCount'
	]
	shares = [
		'objectClass',
		'univentionObjectType',
		'cn',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp'
	]
	dhcp = [
		'entry',
		'children',
		'objectClass',
		'univentionObjectType',
		'dhcpOption',
		'cn',
		'structuralObjectClass',
		'entryUUID',
		'creatorsName',
		'createTimestamp',
		'entryCSN',
		'modifiersName',
		'modifyTimestamp',
	]
	member_server = [
		'krb5KeyVersionNumber',
		'krb5KDCFlags',
		'krb5Key',
		'krb5PasswordEnd',
		'sambaAcctFlags',
		'sambaPwdLastSet',
		'sambaLMPassword',
		'sambaNTPassword',
		'shadowLastChange',
		'shadowMax',
		'userPassword',
		'pwhistory',
		'sambaPwdCanChange',
		'sambaPwdMustChange',
		'sambaPasswordHistory',
	]


class Access(object):
	Read = 'read'
	Write = 'write'
	none = 'none'
	Allowed = 'ALLOWED'
	Denied = 'DENIED'


class LDAPACLTestMatrix(object):

	def __init__(self, auth_dn, default):
		self.auth_dn = auth_dn
		self.default = default
		self.matrix = {}

	def assert_acl(self, target_dn, access, attr, access_allowance=None):
		"""Test ACL rule:\n
		:param target_dn: Target dn to test access to
		:type target_dn: ldap object dn
		:param attrs: names of the attributes to test acl against
		:type attrs: list of str
		:param access: type of access
		:type access: str=Access.Read Access.Write or Access.none
		"""
		access_allowance = access_allowance if access_allowance else Access.Allowed
		# print '\n * Authdn = %s\n * Targetdn = %s\n * Attribute = %s\n * Access = %s' % (
		# 		self.auth_dn, target_dn, attr, access)
		cmd = ['slapacl', '-f', '/etc/ldap/slapd.conf', '-D', '%(auth_dn)s', '-b', '%(target_dn)s', '%(attr)s/%(access)s', '-d', '0']
		argdict = {'auth_dn': self.auth_dn, 'target_dn': target_dn, 'access': access, 'attr': attr}
		out, err = run_commands([cmd], argdict)[0]
		if err:
			result = [x for x in err.split('\n') if (Access.Allowed in x or Access.Denied in x)][0]
			if result:
				if access_allowance not in result:
					return False
				else:
					return True
		else:
			raise FailCmd('command %r was not executed successfully' % cmd)

	def walkThroughContainer(self, base):
		lo = getMachineConnection()
		for container in lo.search(base=base, attr=['*', '+']):
			yield container

	def get_all_attributes_as_dict(self, base):
		"""
		return dict = {
			dn1:[attibute1,	attribute2, ..	],
			dn2:[attibute1,	attribute2, ..	],
			..
			}
		"""
		containers = list(self.walkThroughContainer(base))
		return dict([(y, [k for k in x]) for y, x in containers])

	def gather_attributes_as_set(self, base_list):
		result = set(['children', 'entry'])
		for base in base_list:
			containers = list(self.walkThroughContainer(base))
			s = set([u for x, y in containers for u in y])
			result = result | s
		return result

	def run(self, base_list):
		attrs = self.gather_attributes_as_set(base_list)
		for base in base_list:
			containers = self.walkThroughContainer(base)
			for target_dn, _ in containers:
				for attr in attrs:
					access = Access.none
					if self.assert_acl(target_dn, Access.Write, attr):
						access = Access.Write
					elif self.assert_acl(target_dn, Access.Read, attr):
						access = Access.Read
					expected_access = self.get_attribute_access(target_dn, attr)
					if access != expected_access:
						print '\nDIFFER= User="%s", tried to access Object="%s", Attr="%s", expected="%s", result="%s"' % (self.auth_dn, target_dn, attr, expected_access, access)
					else:
						print 'SAME= User="%s", tried to access Object="%s", Attr="%s", expected="%s", result="%s"' % (self.auth_dn, target_dn, attr, expected_access, access)

	def get_attribute_access(self, target_dn, attr):
		if target_dn in self.matrix:
			return self.matrix[target_dn].get(attr, self.default)
		else:
			return self.default

	def addDn(self, target_dn, attrs, access=Access.none):
		print 'adding target_dn= %s' % (target_dn,)
		for attr in attrs:
			print '\tattr= %s,\taccess= %s' % (attr, access)
			self.matrix.setdefault(target_dn, {})[attr] = access
		print

	def addSubtree(self, container, attrs, access=Access.none):
		for target_dn, _ in self.walkThroughContainer(container):
			self.addDn(target_dn, attrs, access)

	def add_staff_target_dns(self, dn_access_list):
		for (add_type, dn, attrs, access) in dn_access_list:
			if add_type == 0:
				self.addDn(dn, attrs, access)
			else:
				self.addSubtree(dn, attrs, access)


def main():
	with utu.UCSTestSchool() as schoolenv:
		with ucr_test.UCSTestConfigRegistry() as ucr:
			base_dn = ucr.get('ldap/base')
			school, school_dn = schoolenv.create_ou(name_edudc=ucr.get('hostname'))

			tea, tea_dn = schoolenv.create_user(school, is_teacher=True)
			tea_staff, tea_staff_dn = schoolenv.create_user(school, is_teacher=True, is_staff=True)
			staff, staff_dn = schoolenv.create_user(school, is_staff=True)
			stu, stu_dn = schoolenv.create_user(school)
			school_admin, school_admin_dn = schoolenv.create_school_admin(school)

			open_ldap_co = schoolenv.open_ldap_connection()
			# importing 2 random computers
			computers = Computers(open_ldap_co, school, 1, 0, 0)
			created_computers = computers.create()
			computers_dns = computers.get_dns(created_computers)
			computers_hostnames = computers.get_hostnames(created_computers)
			computers_hostnames = [x[:-1] for x in computers_hostnames]
			room = ComputerRoom(school, host_members=computers_dns)
			room.add()

			room_container_dn = 'cn=raeume,cn=groups,%s' % school_dn
			shares_dn = 'cn=shares,%s' % school_dn

			teacher_group2_dn = 'cn=lehrer-%s,cn=groups,%s' % (school, school_dn)
			student_group2_dn = 'cn=schueler-%s,cn=groups,%s' % (school, school_dn)

			teacher_group_dn = 'cn=lehrer,cn=groups,%s' % school_dn
			student_group_dn = 'cn=schueler,cn=groups,%s' % school_dn

			gid_temp_dn = 'cn=gid,cn=temporary,cn=univention,%s' % base_dn
			gidNumber_temp_dn = 'cn=gidNumber,cn=temporary,cn=univention,%s' % base_dn
			sid_temp_dn = 'cn=sid,cn=temporary,cn=univention,%s' % base_dn
			groupName_temp_dn = 'cn=groupName,cn=temporary,cn=univention,%s' % base_dn
			mac_temp_dn = 'cn=mac,cn=temporary,cn=univention,%s' % base_dn

			global_univention_dn = 'cn=univention,%s' % base_dn
			global_policies_dn = 'cn=policies,%s' % base_dn
			global_dns_dn = 'cn=dns,%s' % base_dn
			global_groups_dn = 'cn=groups,%s' % base_dn

			dhcp_dn = 'cn=%s,cn=%s,cn=dhcp,%s' % (computers_hostnames[0], school, base_dn)

			staff_dn_access_list = [
				# 0 for base, 1 for subtree
				(0, stu_dn, Attributes.user, Access.Write),
				(0, room_container_dn, Attributes.container, Access.Write),
				(0, room.dn(), Attributes.room, Access.Write),

				(1, gid_temp_dn, Attributes.temp_tree, Access.Write),
				(1, gidNumber_temp_dn, Attributes.temp_tree, Access.Write),
				(1, sid_temp_dn, Attributes.temp_tree, Access.Write),
				(1, groupName_temp_dn, Attributes.temp_tree, Access.Write),
				(1, mac_temp_dn, Attributes.temp_tree, Access.Write),

				(0, gid_temp_dn, Attributes.temp, Access.Read),
				(0, gidNumber_temp_dn, Attributes.temp, Access.Read),
				(0, sid_temp_dn, Attributes.temp, Access.Read),
				(0, groupName_temp_dn, Attributes.temp, Access.Read),
				(0, mac_temp_dn, Attributes.temp, Access.Read),
				(0, gidNumber_temp_dn, Attributes.gid_temp, Access.Write),

				(1, global_univention_dn, Attributes.global_containers, Access.Write),
				(1, global_policies_dn, Attributes.global_containers, Access.Write),
				(1, global_dns_dn, Attributes.global_containers, Access.Write),
				(1, global_groups_dn, Attributes.global_containers, Access.Write),

				(1, global_univention_dn, Attributes.global_containers, Access.Read),
				(1, global_policies_dn, Attributes.global_containers, Access.Read),
				(1, global_dns_dn, Attributes.global_containers, Access.Read),
				(1, global_groups_dn, Attributes.global_containers, Access.Read),
			]
			mat = LDAPACLTestMatrix(tea_dn, Access.Read)
			mat.add_staff_target_dns(staff_dn_access_list)
			mat.run([
				school_dn,
				global_univention_dn,
				global_policies_dn,
				global_dns_dn,
				global_groups_dn,
				gid_temp_dn,
				gidNumber_temp_dn,
				sid_temp_dn,
				groupName_temp_dn,
				mac_temp_dn
			])


if __name__ == '__main__':
	main()
