#!/usr/share/ucs-test/runner python
## desc: Test users/user and users/ldap bcrypt password handling
## tags: [udm]
## roles: [domaincontroller_master]
## exposure: dangerous
## packages: [python-univention-directory-manager]
## bugs: [52693]

from __future__ import print_function
import univention.testing.utils as utils
import univention.testing.udm as udm_test
import univention.testing.ucr as ucr_test
import univention.testing.strings as uts
from univention.config_registry import handler_set
import atexit
import pytest

import univention.admin.uldap
import univention.admin.uexceptions

if __name__ == '__main__':
	atexit.register(utils.restart_slapd)

	with udm_test.UCSTestUDM() as udm, ucr_test.UCSTestConfigRegistry() as ucr:
		handler_set(['ldap/pw-bcrypt=true'])
		handler_set(['password/hashing/bcrypt=true'])
		utils.restart_slapd()

		for module in ['users/user', 'users/ldap']:
			lo = utils.get_ldap_connection()
			name = uts.random_username()
			attr = dict(password='univention', username=name, lastname='test')
			dn = udm.create_object(module, wait_for_replication=True, check_for_drs_replication=True, wait_for=True, **attr)

			ldap_o = lo.search('uid={}'.format(name), attr=['userPassword', 'pwhistory'])[0]
			assert ldap_o[1]['userPassword'][0].startswith('{BCRYPT}'), ldap_o
			assert ldap_o[1]['pwhistory'][0].split()[0].startswith('{BCRYPT}'), ldap_o

			# authentication
			univention.admin.uldap.access(binddn=dn, bindpw='univention')
			with pytest.raises(univention.admin.uexceptions.authFail):
				univention.admin.uldap.access(binddn=dn, bindpw='univention1')

			# password change
			udm.modify_object(module, dn=dn, password='univention1')
			ldap_o = lo.search('uid={}'.format(name), attr=['userPassword', 'pwhistory'])[0]
			assert ldap_o[1]['userPassword'][0].startswith('{BCRYPT}'), ldap_o
			assert ldap_o[1]['pwhistory'][0].split()[0].startswith('{BCRYPT}'), ldap_o
			assert ldap_o[1]['pwhistory'][0].split()[1].startswith('{BCRYPT}'), ldap_o
			univention.admin.uldap.access(binddn=dn, bindpw='univention1')

			# password history
			# TODO how can we check univention.admin.uexceptions.pwalreadyused?
			with pytest.raises(udm_test.UCSTestUDM_ModifyUDMObjectFailed):
				udm.modify_object(module, dn=dn, password='univention1')

			# mixed password history
			handler_set(['password/hashing/bcrypt=false'])
			udm.stop_cli_server()
			udm.modify_object(module, dn=dn, password='univention2')
			ldap_o = lo.search('uid={}'.format(name), attr=['userPassword', 'pwhistory'])[0]
			assert not ldap_o[1]['userPassword'][0].startswith('{BCRYPT}'), ldap_o
			assert ldap_o[1]['pwhistory'][0].split()[0].startswith('{BCRYPT}'), ldap_o
			assert ldap_o[1]['pwhistory'][0].split()[1].startswith('{BCRYPT}'), ldap_o
			assert not ldap_o[1]['pwhistory'][0].split()[2].startswith('{BCRYPT}'), ldap_o
			with pytest.raises(udm_test.UCSTestUDM_ModifyUDMObjectFailed):
				udm.modify_object(module, dn=dn, password='univention')
			with pytest.raises(udm_test.UCSTestUDM_ModifyUDMObjectFailed):
				udm.modify_object(module, dn=dn, password='univention1')
			with pytest.raises(udm_test.UCSTestUDM_ModifyUDMObjectFailed):
				udm.modify_object(module, dn=dn, password='univention2')

			# and back
			handler_set(['password/hashing/bcrypt=true'])
			udm.stop_cli_server()
			udm.modify_object(module, dn=dn, password='univention4')
			ldap_o = lo.search('uid={}'.format(name), attr=['userPassword', 'pwhistory'])[0]
			assert ldap_o[1]['userPassword'][0].startswith('{BCRYPT}'), ldap_o

			# disable
			univention.admin.uldap.access(binddn=dn, bindpw='univention4')
			udm.modify_object(module, dn=dn, disabled='1')
			with pytest.raises(univention.admin.uexceptions.authFail):
				univention.admin.uldap.access(binddn=dn, bindpw='univention4')
			udm.modify_object(module, dn=dn, disabled='0')
			univention.admin.uldap.access(binddn=dn, bindpw='univention4')

			# 2a variant and cost factor
			handler_set(['password/hashing/bcrypt/prefix=2a'])
			handler_set(['password/hashing/bcrypt/cost_factor=7'])
			udm.stop_cli_server()
			udm.modify_object(module, dn=dn, password='univention5')
			ldap_o = lo.search('uid={}'.format(name), attr=['userPassword', 'pwhistory'])[0]
			assert ldap_o[1]['userPassword'][0].startswith('{BCRYPT}$2a$07$'), ldap_o
			univention.admin.uldap.access(binddn=dn, bindpw='univention5')
