<?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'; /** * Base class for API tokens form function tests. */ class testFormApiTokens extends CWebTest { /** * Attach MessageBehavior to the test. * * @return array */ public function getBehaviors() { return [ 'class' => CMessageBehavior::class ]; } const UPDATE_TOKEN = 'Admin reference token'; // Token for update. const DELETE_TOKEN = 'Token to be deleted'; // Token for deletion. const USER_ZABBIX_TOKEN = 'user-zabbix token'; // Token to be updated that belongs to user-zabbix. public static $tokenid; /** * Function retrieves the tokenid based on token name. * * @param string $token_name The name of the token for which the ID is obtained. * @param boolean $return Flag that specifies whether token id should be returned by this method. * * @return string */ public function getTokenId($token_name = self::UPDATE_TOKEN) { self::$tokenid = CDBHelper::getValue('SELECT tokenid FROM token WHERE name='.zbx_dbstr($token_name)); return self::$tokenid; } /** * Function that checks the layout of the API token configuration form in Administration or User settings section. * * @param string $source Section from which the scenario is executed. */ public function checkTokensFormLayout($source) { $this->page->login()->open('zabbix.php?action='.(($source === 'user settings') ? 'user.token.edit' : 'token.edit')); $this->page->assertTitle('API tokens'); $this->page->assertHeader('API tokens'); $form = $this->query('id:token_form')->asForm()->one(); foreach (['Name' => '64', 'Description' => '65535'] as $field_name => $maxlength) { $field = $form->getField($field_name); $this->assertEquals('', $field->getValue()); $this->assertEquals($maxlength, $field->getAttribute('maxlength')); } // Check the presence of User field and that it is empty by default if it exists. if ($source === 'administration') { $this->assertEquals('', $form->getField('User')->getValue()); } else { $this->assertFalse($form->query('xpath://label[text()="User"]')->one(false)->isDisplayed()); } // Check that "Set expiration date and time" checkbox is set by default. $expiration_checkbox = $form->getField('Set expiration date and time'); $this->assertTrue($expiration_checkbox->getValue()); $expires_at = $form->getField('Expires at')->query('id:expires_at')->one(); $this->assertEquals('',$field->getValue()); $this->assertEquals('255', $expires_at->getAttribute('maxlength')); $this->assertEquals('YYYY-MM-DD hh:mm:ss', $expires_at->getAttribute('placeholder')); $calendar = $form->query('id:expires_at_calendar')->one(); $this->assertTrue($calendar->isClickable()); $this->assertEquals('toggleCalendar(this, "expires_at", "Y-m-d H:i:s");', $calendar->getAttribute('onclick')); // Check that "Expires at" field is removed if "Set expiration date and time" is not set. $expiration_checkbox->set(false); $this->assertFalse($form->getField('Expires at')->isVisible()); $this->assertTrue($form->getField('Enabled')->getValue()); foreach($form->query('button', ['Add', 'Cancel'])->all() as $button) { $this->assertTrue($button->isClickable()); } } /** * Function checks the layout in Token regenerate form. * * @param string $source Section from which the scenario is executed. * @param integer $tokenid ID of the token for which the regenerate form is opened. */ public function checkTokensRegenerateFormLayout($source) { $values = [ 'Name:' => 'Admin reference token', 'User:' => 'Admin (Zabbix Administrator)', 'Description:' => 'admin token to be used in update scenarios', 'Expires at:' => '2026-12-31 23:59:59' ]; // User field is not present in User settings => Api tokens configuration form. if ($source === 'user settings') { unset($values['User:']); } $this->page->login()->open('zabbix.php?&tokenid='.self::$tokenid.'&action='.(($source === 'user settings') ? 'user.token.edit' : 'token.edit')); $this->query('button:Regenerate')->one()->waitUntilClickable()->click(); $this->page->acceptAlert(); $this->page->waitUntilReady(); $this->page->assertTitle('API tokens'); $this->page->assertHeader('API tokens'); $this->assertMessage(TEST_GOOD, 'API token updated'); $form = $this->query('id:token_form')->asForm()->one(); $this->assertEquals(true, $form->getField('Enabled:')->getValue()); foreach ($values as $name => $value) { $this->assertEquals($value, $form->getField($name)->getText()); } if ($source === 'user settings') { $this->assertFalse($form->query('xpath://label[text()="User"]')->one(false)->isDisplayed()); } // Check Auth token field. $auth_token = $form->getField('Auth token:'); $this->checkAuthToken($auth_token, null); // Check the hintbox text in the Auth token field. $auth_token->query('xpath:./a[@data-hintbox]')->one()->click(); $hintbox_text = $this->query('xpath://div[@class="overlay-dialogue wordbreak"]')->one()->waitUntilVisible()->getText(); $this->assertEquals('Make sure to copy the auth token as you won\'t be able to view it after the page is closed.', $hintbox_text); $this->assertTrue($form->query('button:Close')->one()->isClickable()); } /** * Function performs creation, update or regeneration of auth token and checks the result. * * @param array $data data provider * @param string $url the URL that leads to the form where the action needs to be performed * @param string $action action that needs to be executed within this method */ public function checkTokensAction($data, $url, $action) { if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_BAD) { $sql = 'SELECT * FROM token ORDER BY tokenid'; $old_hash = CDBHelper::getHash($sql); } $this->page->login()->open($url); $form = $this->query('id:token_form')->asForm()->one(); // Fill form or press appropriate button depending on the action. if ($action === 'regenerate') { $old_token = CDBHelper::getValue('SELECT token FROM token WHERE tokenid='.$data['tokenid']); $form->query('button:Regenerate')->one()->click(); $this->page->acceptAlert(); } elseif ($action === 'update' && array_key_exists('User', $data['fields'])) { $userless_data = $data['fields']; // Field "User" is read only when editing an API token. $this->assertFalse($form->getField('User')->isEnabled()); unset($userless_data['User']); $form->fill($userless_data); $form->submit(); } else { $form->fill($data['fields']); $form->submit(); } if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_BAD) { $title = ($action === 'create') ? 'Cannot add API token' : 'Cannot update API token'; $this->assertMessage(TEST_BAD, $title, $data['error_details']); // Check that DB hash is not changed. $this->assertEquals($old_hash, CDBHelper::getHash($sql)); } else { $title = ($action === 'create') ? 'API token added' : 'API token updated'; $this->assertMessage(TEST_GOOD, $title); // Substitute user name with full name in the data provider for reference. if (array_key_exists('full_name', $data)) { $data['fields']['User'] = $data['full_name']; } if ($action !== 'update') { $form->invalidate(); // Prepare the data provider for token generate view. $generate_data = $data['fields']; unset($generate_data['Set expiration date and time']); // Check warning in case if token is already expired. if (CTestArrayHelper::get($data, 'already_expired')) { $form->getField('Expires at:')->query('xpath:./a[@data-hintbox]')->one()->click(); $hintbox_text = $this->query('xpath://div[@class="overlay-dialogue wordbreak"]')->one()->waitUntilVisible()->getText(); $this->assertEquals('The token has expired. Please update the expiry date to use the token.', $hintbox_text); // In case if token is expired an empty space (separator) is added to the value in token generate form. $generate_data['Expires at'] = $generate_data['Expires at'].' '; } foreach ($generate_data as $name => $value) { if ($name === 'Enabled') { $this->assertEquals($value, $form->getField('Enabled:')->getValue()); } else { $this->assertEquals($value, $form->getField($name.':')->getText()); } } // Check Auth token field. $original_token = ($action === 'regenerate') ? $old_token : null; $auth_token = $form->getField('Auth token:'); $this->checkAuthToken($auth_token, $original_token); $form->query('button:Close')->one()->click(); } // Open token configuration and check field values. $this->query('xpath://a[text()='.CXPathHelper::escapeQuotes($data['fields']['Name']).']')->one()->click(); $this->page->waitUntilReady(); $form->invalidate(); if (array_key_exists('User', $data['fields'])) { $this->assertFalse($form->getField('User')->isEnabled()); } $form->checkValue($data['fields']); } } /** * Function that checks that no database changes occurred if nothing was actually changed during token update. * * @param string $url URL that leads to the form to be updated. */ public function checkTokenSimpleUpdate($url) { $sql = 'SELECT * FROM token ORDER BY tokenid'; $old_hash = CDBHelper::getHash($sql); $this->page->login()->open($url); $this->query('button:Update')->one()->waitUntilClickable()->click(); // Check that DB hash is not changed. $this->assertEquals($old_hash, CDBHelper::getHash($sql)); } /** * Function that check that no database changes are occurred if token create or update action is cancelled. * * @param string $url URL that leads to the form to be updated. * @param string $username user name in User field of the form if form opened from Administration section. */ public function checkTokenCancel($url, $username = null ) { $sql = 'SELECT * FROM token ORDER BY tokenid'; $old_hash = CDBHelper::getHash($sql); $data = [ 'Name' => 'Token to be cancelled', 'Description' => 'Token to be cancelled', 'Set expiration date and time' => true, 'Expires at' => '2038-01-01 00:00:00', 'Enabled' => false ]; $this->page->login()->open($url); $form = $this->query('id:token_form')->asForm()->one(); if ($username) { $data['User'] = $username; } $form->fill($data); $this->query('button:Cancel')->one()->waitUntilClickable()->click(); // Check that DB hash is not changed. $this->assertEquals($old_hash, CDBHelper::getHash($sql)); } /** * Function that checks token deletion from token edit form. * * @param string $url URL that leads to the form to be updated. * @param string $token_name The name of the token to be deleted. */ public function checkTokenDelete($url, $token_name) { $sql = 'SELECT tokenid FROM token WHERE name = '.zbx_dbstr($token_name); $this->page->login()->open($url); $this->query('button:Delete')->one()->waitUntilClickable()->click(); $this->page->acceptAlert(); $this->page->waitUntilReady(); // Check that DB hash is not changed. $this->assertEquals(0, CDBHelper::getCount($sql)); } /** * Function that checks the token string. * * @param CElement $auth_token page element that contains the token string. * @param string $original_token token string that belonged to the token before token regeneration. */ private function checkAuthToken($auth_token, $original_token) { // Get token text. $token_text = str_replace(' Copy to clipboard', '', $auth_token->getText()); $this->assertEquals(64, strlen($token_text)); if ($original_token) { $this->assertFalse($original_token === $token_text); } // Check that token string will be copied to clipboard. $clipboard_element = $auth_token->query('xpath:./a[text()="Copy to clipboard"]')->one(); $this->assertEquals('writeTextClipboard("'.$token_text.'")', $clipboard_element->getAttribute('onclick')); } }