<?php
/*
** Zabbix
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

require_once dirname(__FILE__).'/../include/CWebTest.php';
require_once dirname(__FILE__).'/behaviors/CMessageBehavior.php';

/**
 * @onBefore prepareUserData
 *
 * @backup config, users
 */
class testPasswordComplexity extends CWebTest {

	const ACTION_UPDATE = true;
	const OWN_PASSWORD  = true;
	const ADMIN_USERID  = 1;

	/**
	 * Attach MessageBehavior to the test.
	 *
	 * @return array
	 */
	public function getBehaviors() {
		return ['class' => CMessageBehavior::class];
	}

	/**
	 * Id of user that created for future updating.
	 *
	 * @var integer
	 */
	protected static $userid;

	/**
	 * Password for user which is being changed in scenarios.
	 *
	 * @var string
	 */
	protected static $user_password = 'Iamrobot1!';

	/**
	 * Password for Admin which is being changed in scenarios.
	 *
	 * @var string
	 */
	protected static $admin_password = 'zabbix';

	/**
	 * Add user for updating.
	 */
	public function prepareUserData() {
		$response = CDataHelper::call('user.create', [
			[
				'username' => 'update-user',
				'passwd' => 'Iamrobot1!',
				'autologin' => 1,
				'autologout' => 0,
				'roleid' => 1,
				'usrgrps' => [
					[
						'usrgrpid' => '7'
					]
				]
			]
		]);

		$this->assertArrayHasKey('userids', $response);
		self::$userid = $response['userids'][0];
	}

	/**
	 * Check authentication form fields layout.
	 */
	public function testPasswordComplexity_Layout() {
		$this->page->login()->open('zabbix.php?action=authentication.edit');
		$this->page->assertTitle('Configuration of authentication');
		$form = $this->query('name:form_auth')->asForm()->one();

		// Check authentication swithcher options and default value.
		$auth_radio = $form->getField('Default authentication')->asSegmentedRadio();
		$this->assertEquals(['Internal', 'LDAP'], $auth_radio->getLabels()->asText());
		$this->assertEquals('Internal', $auth_radio->getSelected());

		// Check that 'Password policy' header presents.
		$this->assertTrue($form->query('xpath://h4[text()="Password policy"]')->exists());

		$this->assertEquals(2, $form->getField('Minimum password length')->getAttribute('maxlength'));
		// Check default texts in hint-boxes.
		$hintboxes = [
			[
				'field' => 'Password must contain',
				'text' => "Password requirements:".
						"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
						"\nmust contain at least one digit (0-9)".
						"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)"
			],
			[
				'field' => 'Avoid easy-to-guess passwords',
				'text' => "Password requirements:".
						"\nmust not contain user's name, surname or username".
						"\nmust not be one of common or context-specific passwords"
			]
		];

		foreach ($hintboxes as $hintbox) {
			// Summon the hint-box.
			$form->query('xpath://label[text()='.zbx_dbstr($hintbox['field']).']//a')->one()->click();
			$hint = $form->query('xpath://div[@class="overlay-dialogue wordbreak"]')->waitUntilPresent();

			// Assert text.
			$this->assertEquals($hintbox['text'], $hint->one()->getText());

			// Close the hint-box.
			$hint->one()->query('xpath:.//button[@class="overlay-close-btn"]')->one()->click();
			$hint->waitUntilNotPresent();
		}

		// Assert default values in form.
		$default_values = [
			'Minimum password length' => '8',
			'id:passwd_check_rules_case' => false,
			'id:passwd_check_rules_digits' => false,
			'id:passwd_check_rules_special' => false,
			'id:passwd_check_rules_simple' => true
		];
		foreach ($default_values as $field => $value) {
			$this->assertEquals($value, $form->getField($field)->getValue());
		}

		// Check default values in DB.
		$this->assertEquals([['passwd_min_length' => 8, 'passwd_check_rules' => 8]],
				CDBHelper::getAll('SELECT passwd_min_length, passwd_check_rules FROM config')
		);
	}

	public function getFormValidationData() {
		return [
			[
				[
					'expected' => TEST_BAD,
					'fields' => ['Minimum password length' => '0']
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => ['Minimum password length' => '71']
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => ['Minimum password length' => '-ab']
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => ['Minimum password length' => '!@']
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => ['Minimum password length' => '']
				]
			],
			[
				[
					'fields' => ['Minimum password length' => '1'],
					'db_passwd_min_length' => 1
				]
			],
			[
				[
					'fields' => ['Minimum password length' => '50'],
					'db_passwd_min_length' => 50
				]
			],
			[
				[
					'fields' => ['Minimum password length' => '70'],
					'db_passwd_min_length' => 70
				]
			],
			// Negative number will be converted to positive when focus-out.
			[
				[
					'fields' => ['Minimum password length' => '-8'],
					'db_passwd_min_length' => 8
				]
			]
		];
	}

	/**
	 * Check authentication form fields validation.
	 *
	 * @dataProvider getFormValidationData
	 */
	public function testPasswordComplexity_FormValidation($data) {
		if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_BAD) {
			$old_hash = CDBHelper::getHash('SELECT * FROM config');
		}

		$this->page->login()->open('zabbix.php?action=authentication.edit');
		$form = $this->query('name:form_auth')->asForm()->one();
		$form->fill($data['fields']);
		$form->submit();

		if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_BAD) {
			$this->assertMessage(TEST_BAD, 'Cannot update authentication',
					'Invalid parameter "/passwd_min_length": value must be one of 1-70.'
			);
			$this->assertEquals($old_hash, CDBHelper::getHash('SELECT * FROM config'));
		}
		else {
			$this->assertMessage(TEST_GOOD, 'Authentication settings updated');
			// Check length fields saved in db, other fields remained default.
			$db_expected = ['passwd_min_length' => $data['db_passwd_min_length'], 'passwd_check_rules' => 8];
			$this->assertEquals([$db_expected],
					CDBHelper::getAll('SELECT passwd_min_length, passwd_check_rules FROM config')
			);
		}
	}

	public function getCommonPasswordData() {
		return [
			[
				// Check default password complexity settings.
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'iamrobot',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords"
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '1',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 0,
					'Password' => 'a'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '1',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 15,
					'Password' => 'aA1!',
					'hint' => "Password requirements:".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
							"\nmust contain at least one digit (0-9)".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '1',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 15,
					'Password' => '',
					'hint' => "Password requirements:".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
							"\nmust contain at least one digit (0-9)".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => 'Incorrect value for field "Password": cannot be empty.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '1',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 15,
					'Password' => 'a',
					'hint' => "Password requirements:".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
							"\nmust contain at least one digit (0-9)".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one lowercase and one uppercase Latin letter.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '1',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 0,
					'Password' => '',
					'error' => 'Incorrect value for field "Password": cannot be empty.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '3',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 1,
					'Password' => 'Tes',
					'hint' => "Password requirements:".
							"\nmust be at least 3 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '3',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 1,
					'Password' => 'Te',
					'hint' => "Password requirements:".
							"\nmust be at least 3 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)",
					'error' => 'Incorrect value for field "/1/passwd": must be at least 3 characters long.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '2',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 1,
					'Password' => 'tes',
					'hint' => "Password requirements:".
							"\nmust be at least 2 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one lowercase and one uppercase Latin letter.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '70',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 0,
					'Password' => str_repeat('a', 70),
					'hint' => "Password requirements:".
							"\nmust be at least 70 characters long"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '70',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 0,
					'Password' => str_repeat('a', 69),
					'hint' => "Password requirements:".
							"\nmust be at least 70 characters long",
					'error' => 'Incorrect value for field "/1/passwd": must be at least 70 characters long.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '70',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 0,
					'Password' => str_repeat('a', 71),
					'hint' => "Password requirements:".
							"\nmust be at least 70 characters long"
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '70',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 1,
					'Password' => str_repeat('a', 35).str_repeat('A', 36),
					'hint' => "Password requirements:".
							"\nmust be at least 70 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '70',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 1,
					'Password' => str_repeat('a', 80),
					'hint' => "Password requirements:".
							"\nmust be at least 70 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one lowercase and one uppercase Latin letter.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 2,
					'Password' => 'secure_password',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one digit.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 3,
					'Password' => 'Secure_Password',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
							"\nmust contain at least one digit (0-9)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one digit.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 3,
					'Password' => 'Secure_Password1',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
							"\nmust contain at least one digit (0-9)"
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 2,
					'Password' => 'secure_password1',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)"
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 2,
					'Password' => '99009900',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)"
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 2,
					'Password' => '12345678',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 10,
					'Password' => '12345678',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => 'Incorrect value for field "/1/passwd": must not be one of common or context-specific passwords.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 6,
					'Password' => 'secure_password1#():}',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 6,
					'Password' => 'securepassword1',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one digit (0-9)".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one special character.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => true,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 6,
					'Password' => 'securepassword#',
					'hint' => "Password requirements:".
								"\nmust be at least 8 characters long".
								"\nmust contain at least one digit (0-9)".
								"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one digit.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 4,
					'Password' => 'securepassword#',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 4,
					'Password' => 'securepassword',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one special character.'
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => false
					],
					'db_passwd_check_rules' => 4,
					'Password' => "( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)",
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)"
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 12,
					'Password' => "zabbix",
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => 'Incorrect value for field "/1/passwd": must be at least 8 characters long.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => true,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 12,
					'Password' => "zabbix",
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust contain at least one special character ( !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => 'Incorrect value for field "/1/passwd": must contain at least one special character.'
				]
			]
		];
	}

	public function getUserPasswordData() {
		return [
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => "zabbix",
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => 'Incorrect value for field "/1/passwd": must not be one of common or context-specific passwords.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => true,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 9,
					'Password' => 'Admin',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust contain at least one lowercase and one uppercase Latin letter (A-Z, a-z)".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not be one of common or context-specific passwords."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'admin',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not be one of common or context-specific passwords."
				]
			]
		];
	}

	public function getAdminPasswordData() {
		return [
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'Admin',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not contain user's name, surname or username."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'admin',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not contain user's name, surname or username."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'admin1',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not contain user's name, surname or username."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '8',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'admin',
					'hint' => "Password requirements:".
							"\nmust be at least 8 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must be at least 8 characters long."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'zabbix',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not contain user's name, surname or username."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'password',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not be one of common or context-specific passwords."
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'password',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords",
					'error' => "Incorrect value for field \"/1/passwd\": must not be one of common or context-specific passwords."
				]
			],
			[
				[
					'auth_fields' => [
						'Minimum password length' => '4',
						'id:passwd_check_rules_case' => false,
						'id:passwd_check_rules_digits' => false,
						'id:passwd_check_rules_special' => false,
						'id:passwd_check_rules_simple' => true
					],
					'db_passwd_check_rules' => 8,
					'Password' => 'securepassword',
					'hint' => "Password requirements:".
							"\nmust be at least 4 characters long".
							"\nmust not contain user's name, surname or username".
							"\nmust not be one of common or context-specific passwords"
				]
			]
		];
	}

	/**
	 * Check user creation with password complexity rules.
	 *
	 * @dataProvider getCommonPasswordData
	 * @dataProvider getUserPasswordData
	 */
	public function testPasswordComplexity_CreateUserPassword($data) {
		$this->checkPasswordComplexity($data, self::$admin_password);
	}

	/**
	 * Check if user changes their own password according to complexity rules.
	 *
	 * @dataProvider getCommonPasswordData
	 * @dataProvider getUserPasswordData
	 */
	public function testPasswordComplexity_ChangeOwnUserPassword($data) {
		$this->checkPasswordComplexity($data, self::$admin_password, self::$userid, self::ACTION_UPDATE,
				self::OWN_PASSWORD, self::$user_password
		);
	}

	/**
	 * Check if Admin changes their own password according to complexity rules.
	 *
	 * @dataProvider getCommonPasswordData
	 * @dataProvider getAdminPasswordData
	 */
	public function testPasswordComplexity_ChangeOwnAdminPassword($data) {
		$this->checkPasswordComplexity($data, self::$admin_password, self::ADMIN_USERID, self::ACTION_UPDATE, self::OWN_PASSWORD);
	}

	/**
	 * Check user update with password complexity rules.
	 *
	 * @dataProvider getCommonPasswordData
	 * @dataProvider getUserPasswordData
	 */
	public function testPasswordComplexity_UpdateUserPassword($data) {
		$this->checkPasswordComplexity($data, self::$admin_password, self::$userid, self::ACTION_UPDATE);
	}

	/**
	 * Check admin user password update accordingly to complexity rules.
	 *
	 * @dataProvider getCommonPasswordData
	 * @dataProvider getAdminPasswordData
	 */
	public function testPasswordComplexity_UpdateAdminPassword($data) {
		$this->checkPasswordComplexity($data, self::$admin_password, self::ADMIN_USERID, self::ACTION_UPDATE);
	}

	/**
	 * Check password complexity rules for user creation or update.
	 *
	 * @param array     $data       data provider
	 * @param string    $admin_password    password used for Admin user login
	 * @param int       $userid            id of the user whose password is changed
	 * @param boolean   $update            false if create, true if update
	 * @param $own      $own               true if user changes their password themselves
	 */
	private function checkPasswordComplexity($data, $admin_password, $userid = null, $update = false, $own = false,
			$user_password = null) {
		if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_BAD) {
			$old_hash = CDBHelper::getHash('SELECT * FROM users ORDER BY userid');
		}

		$this->page->userLogin('Admin', $admin_password);
		$this->page->open('zabbix.php?action=authentication.edit');
		$auth_form = $this->query('name:form_auth')->asForm()->waitUntilPresent()->one();
		$auth_form->fill($data['auth_fields']);
		$auth_form->submit();
		$this->page->waitUntilReady();
		$this->assertMessage(TEST_GOOD, 'Authentication settings updated');
		$this->assertEquals($data['db_passwd_check_rules'],
				CDBHelper::getValue('SELECT passwd_check_rules FROM config')
		);

		if ($update) {
			if ($own) {
				if ($userid !== 1) {
					$this->page->userLogin('update-user', $user_password);
				}

				$this->page->open('zabbix.php?action=userprofile.edit');
				$this->clickChangePassword();
			}
			else {
				$this->page->open('zabbix.php?action=user.edit&userid='.$userid);
				$this->clickChangePassword();
			}
		}
		else {
			$this->page->open('zabbix.php?action=user.edit');
		}

		// Check user password creation accordingly to complexity settings.
		$user_form = $this->query('name:user_form')->asForm()->waitUntilPresent()->one();
		$username = ($userid === 1)
			? 'Admin'
			: ($update ? 'update-user' : 'username'.time());

		if ($update === false && $own === false) {
			$user_form->fill([
				'Username' => $username,
				'Groups' => ['Zabbix administrators']
			]);
		}

		if (array_key_exists('hint', $data)) {
			// Summon hint-box and assert text accordingly to password complexity settings, then close hint-box.
			$user_form->query('xpath://label[text()="Password"]//a')->one()->click();
			$hint = $user_form->query('xpath://div[@class="overlay-dialogue wordbreak"]')->waitUntilPresent();
			$this->assertEquals($data['hint'], $hint->one()->getText());
			$hint->one()->query('xpath:.//button[@class="overlay-close-btn"]')->one()->click();
			$hint->waitUntilNotPresent();
		}
		else {
			// If password can be 1 symbol long and doesn't have any complexity rules hint is not shown at all.
			$this->assertFalse($user_form->query('xpath://label[text()="Password"]//a')->exists());
		}

		$user_form->fill([
			'Password' => $data['Password'],
			'Password (once again)' => $data['Password']
		]);

		if ($update === false) {
			$user_form->selectTab('Permissions');
			$user_form->fill(['Role' => 'User role']);
		}

		$user_form->submit();

		if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_BAD) {
			$this->assertMessage(TEST_BAD, 'Cannot '.($update ? 'update' : 'add').' user', $data['error']);
			$this->assertEquals($old_hash, CDBHelper::getHash('SELECT * FROM users ORDER BY userid'));
		}
		else {
			$this->assertMessage(TEST_GOOD, 'User '.($update ? 'updated' : 'added'));

			// Check user saved in db.
			$this->assertEquals(1, CDBHelper::getCount('SELECT * FROM users WHERE username ='.zbx_dbstr($username)));

			// Check success login with new password.
			$this->page->userLogin($username, $data['Password']);
			$this->assertTrue($this->query('xpath://a[@title='.zbx_dbstr(($userid === 1) ? 'Admin (Zabbix Administrator)'
					: $username).' and text()="User settings"]')->exists()
			);

			// Write new password for next case.
			if (($userid === 1) && ($own || $update)) {
				self::$admin_password = $data['Password'];
			}
			elseif ($own) {
				self::$user_password = $data['Password'];
			}
		}
	}

	/**
	 * Click button "Change password" and wait until both password fields are editable.
	 */
	private function clickChangePassword() {
		$this->query('button:Change password')->waitUntilClickable()->one()->click();
		$this->query('id:password1')->waitUntilPresent()->one();
		$this->query('id:password2')->waitUntilPresent()->one();
	}
}