From 701e98671d8ec71fb60cf4f7a12630e469c809c2 Mon Sep 17 00:00:00 2001 From: Andrejs Verza Date: Mon, 7 Mar 2022 14:53:35 +0200 Subject: ..F....... [ZBX-19583] unified date format across datepickers Merge in ZBX/zabbix from feature/ZBX-19583-6.0 to release/6.0 * commit 'aee2b261169543db8c3562e54e5fb64e4269e6cf': .......... [ZBX-19583] updated unit tests ..F....... [ZBX-19583] added uint64 input validation rule ..F....... [ZBX-19583] fixed typos ..F....... [ZBX-19583] fixed undefined index errors in forged requests ..F....... [ZBX-19583] fixed absolute time parser .......... [ZBX-19583] added new test cases for CAbsoluteTimeParser class .......... [ZBX-19583] added new test cases for CValidationRule class ..F....... [ZBX-19583] unified date format across datepickers (cherry picked from commit 6519662e85d65e77fbd74360837610241c9d6630) --- ChangeLog.d/bugfix/ZBX-19583 | 1 + ui/app/controllers/CControllerTokenCreate.php | 51 ++++++++++-- ui/app/controllers/CControllerTokenEdit.php | 2 +- ui/app/controllers/CControllerTokenUpdate.php | 48 ++++++++++-- ui/app/controllers/CControllerUserTokenEdit.php | 2 +- ui/app/views/administration.token.edit.php | 2 +- ui/app/views/administration.user.token.edit.php | 2 +- ui/include/classes/parsers/CAbsoluteTimeParser.php | 15 ++-- ui/include/classes/parsers/CValidationRule.php | 91 +++++++++++++++++----- ui/include/classes/validators/CNewValidator.php | 49 ++++++++++++ .../classes/parsers/CAbsoluteTimeParserTest.php | 17 +++- .../classes/parsers/CValidationRuleTest.php | 15 ++++ 12 files changed, 251 insertions(+), 44 deletions(-) create mode 100644 ChangeLog.d/bugfix/ZBX-19583 diff --git a/ChangeLog.d/bugfix/ZBX-19583 b/ChangeLog.d/bugfix/ZBX-19583 new file mode 100644 index 00000000000..cd097dd4572 --- /dev/null +++ b/ChangeLog.d/bugfix/ZBX-19583 @@ -0,0 +1 @@ +..F....... [ZBX-19583] unified date format across datepickers (averza) diff --git a/ui/app/controllers/CControllerTokenCreate.php b/ui/app/controllers/CControllerTokenCreate.php index 58efb9cc7f0..06649cc245a 100644 --- a/ui/app/controllers/CControllerTokenCreate.php +++ b/ui/app/controllers/CControllerTokenCreate.php @@ -27,16 +27,46 @@ class CControllerTokenCreate extends CController { 'description' => 'db token.description', 'userid' => 'db users.userid|required', 'expires_state' => 'in 0,1|required', - 'expires_at' => 'range_time', + 'expires_at' => 'abs_time', 'status' => 'db token.status|required|in '.ZBX_AUTH_TOKEN_ENABLED.','.ZBX_AUTH_TOKEN_DISABLED, 'action_src' => 'fatal|required|in token.edit,user.token.edit', 'action_dst' => 'fatal|required|in token.view,user.token.view' ]; + $validation_result = self::VALIDATION_OK; + $ret = $this->validateInput($fields); + if ($ret) { + $fields = []; + + if ($this->getInput('expires_state') == 1) { + $fields['expires_at'] = 'required'; + } + + if ($fields) { + $validator = new CNewValidator($this->getInputAll(), $fields); + + foreach ($validator->getAllErrors() as $error) { + info($error); + } + + if ($validator->isErrorFatal()) { + $validation_result = self::VALIDATION_FATAL_ERROR; + } + elseif ($validator->isError()) { + $validation_result = self::VALIDATION_ERROR; + } + + $ret = $validation_result == self::VALIDATION_OK; + } + } + else { + $validation_result = $this->getValidationError(); + } + if (!$ret) { - switch ($this->getValidationError()) { + switch ($validation_result) { case self::VALIDATION_ERROR: $location = (new CUrl('zabbix.php'))->setArgument('action', $this->getInput('action_src')); $response = new CControllerResponseRedirect($location); @@ -61,12 +91,23 @@ class CControllerTokenCreate extends CController { return $this->checkAccess(CRoleHelper::ACTIONS_MANAGE_API_TOKENS); } + /** + * @throws Exception + */ protected function doAction() { $this->getInputs($token, ['name', 'description', 'userid', 'expires_at', 'status']); - $token['expires_at'] = $this->getInput('expires_state') - ? (new DateTime($token['expires_at']))->getTimestamp() - : 0; + if ($this->getInput('expires_state')) { + $parser = new CAbsoluteTimeParser(); + $parser->parse($token['expires_at']); + + $token['expires_at'] = $parser + ->getDateTime(true) + ->getTimestamp(); + } + else { + $token['expires_at'] = 0; + } $result = API::Token()->create($token); diff --git a/ui/app/controllers/CControllerTokenEdit.php b/ui/app/controllers/CControllerTokenEdit.php index 405a44d822a..608a96920b5 100644 --- a/ui/app/controllers/CControllerTokenEdit.php +++ b/ui/app/controllers/CControllerTokenEdit.php @@ -69,7 +69,7 @@ class CControllerTokenEdit extends CController { $data = $tokens[0]; if ($data['expires_at'] != 0) { - $data['expires_at'] = date(DATE_TIME_FORMAT_SECONDS, (int) $data['expires_at']); + $data['expires_at'] = date(ZBX_FULL_DATE_TIME, (int) $data['expires_at']); $data['expires_state'] = '1'; } else { diff --git a/ui/app/controllers/CControllerTokenUpdate.php b/ui/app/controllers/CControllerTokenUpdate.php index 052c0a59ef2..10f1de673cd 100644 --- a/ui/app/controllers/CControllerTokenUpdate.php +++ b/ui/app/controllers/CControllerTokenUpdate.php @@ -27,17 +27,47 @@ class CControllerTokenUpdate extends CController { 'name' => 'db token.name|required|not_empty', 'description' => 'db token.description', 'expires_state' => 'in 0,1|required', - 'expires_at' => 'range_time', + 'expires_at' => 'abs_time', 'status' => 'db token.status|required|in '.ZBX_AUTH_TOKEN_ENABLED.','.ZBX_AUTH_TOKEN_DISABLED, 'action_src' => 'fatal|required|in token.edit,user.token.edit', 'action_dst' => 'fatal|required|in token.list,user.token.list,token.view,user.token.view', 'regenerate' => 'in 1' ]; + $validation_result = self::VALIDATION_OK; + $ret = $this->validateInput($fields); + if ($ret) { + $fields = []; + + if ($this->getInput('expires_state') == 1) { + $fields['expires_at'] = 'required'; + } + + if ($fields) { + $validator = new CNewValidator($this->getInputAll(), $fields); + + foreach ($validator->getAllErrors() as $error) { + info($error); + } + + if ($validator->isErrorFatal()) { + $validation_result = self::VALIDATION_FATAL_ERROR; + } + elseif ($validator->isError()) { + $validation_result = self::VALIDATION_ERROR; + } + + $ret = $validation_result == self::VALIDATION_OK; + } + } + else { + $validation_result = $this->getValidationError(); + } + if (!$ret) { - switch ($this->getValidationError()) { + switch ($validation_result) { case self::VALIDATION_ERROR: $location = (new CUrl('zabbix.php')) ->setArgument('tokenid', $this->getInput('tokenid')) @@ -67,9 +97,17 @@ class CControllerTokenUpdate extends CController { protected function doAction() { $this->getInputs($token, ['tokenid', 'name', 'description', 'expires_at', 'status']); - $token['expires_at'] = $this->getInput('expires_state') - ? (new DateTime($token['expires_at']))->getTimestamp() - : 0; + if ($this->getInput('expires_state')) { + $parser = new CAbsoluteTimeParser(); + $parser->parse($token['expires_at']); + + $token['expires_at'] = $parser + ->getDateTime(true) + ->getTimestamp(); + } + else { + $token['expires_at'] = 0; + } $result = API::Token()->update($token); diff --git a/ui/app/controllers/CControllerUserTokenEdit.php b/ui/app/controllers/CControllerUserTokenEdit.php index 881689869bb..a84d7dd5f31 100644 --- a/ui/app/controllers/CControllerUserTokenEdit.php +++ b/ui/app/controllers/CControllerUserTokenEdit.php @@ -66,7 +66,7 @@ class CControllerUserTokenEdit extends CController { $data = $tokens[0]; if ($data['expires_at'] != 0) { - $data['expires_at'] = date(DATE_TIME_FORMAT_SECONDS, (int) $data['expires_at']); + $data['expires_at'] = date(ZBX_FULL_DATE_TIME, (int) $data['expires_at']); $data['expires_state'] = '1'; } else { diff --git a/ui/app/views/administration.token.edit.php b/ui/app/views/administration.token.edit.php index cf54131a865..52b7ed0c26a 100644 --- a/ui/app/views/administration.token.edit.php +++ b/ui/app/views/administration.token.edit.php @@ -81,7 +81,7 @@ $token_from_list = (new CFormList()) ) ->addRow((new CLabel(_('Expires at')))->setAsteriskMark(), (new CDateSelector('expires_at', $data['expires_at'])) - ->setDateFormat(DATE_TIME_FORMAT_SECONDS) + ->setDateFormat(ZBX_FULL_DATE_TIME) ->setPlaceholder(_('YYYY-MM-DD hh:mm:ss')) ->setAriaRequired(), 'expires-at-row' diff --git a/ui/app/views/administration.user.token.edit.php b/ui/app/views/administration.user.token.edit.php index 0cf6d3c2e28..9193b1185ab 100644 --- a/ui/app/views/administration.user.token.edit.php +++ b/ui/app/views/administration.user.token.edit.php @@ -62,7 +62,7 @@ $token_from_list = (new CFormList()) ) ->addRow((new CLabel(_('Expires at')))->setAsteriskMark(), (new CDateSelector('expires_at', $data['expires_at'])) - ->setDateFormat(DATE_TIME_FORMAT_SECONDS) + ->setDateFormat(ZBX_FULL_DATE_TIME) ->setPlaceholder(_('YYYY-MM-DD hh:mm:ss')) ->setAriaRequired(), 'expires-at-row' diff --git a/ui/include/classes/parsers/CAbsoluteTimeParser.php b/ui/include/classes/parsers/CAbsoluteTimeParser.php index ea7d847267b..b7c0ba271a4 100644 --- a/ui/include/classes/parsers/CAbsoluteTimeParser.php +++ b/ui/include/classes/parsers/CAbsoluteTimeParser.php @@ -106,19 +106,20 @@ class CAbsoluteTimeParser extends CParser { } /** - * Returns date in "YYYY-MM-DD hh:mm:ss" format. + * Get DateTime object with its value set to either start or end of the period derived from the date/time specified. * - * @param bool $is_start If set to true date will be modified to lowest value, example "2018" will be returned - * as "2018-01-01 00:00:00", otherwise "2018-12-31 23:59:59". + * @param $is_start + * @param DateTimeZone|null $timezone * + * @throws Exception * @return DateTime|null */ - public function getDateTime($is_start) { + public function getDateTime($is_start, DateTimeZone $timezone = null) { if ($this->date === '') { return null; } - $date = new DateTime($this->date); + $date = new DateTime($this->date, $timezone); if ($is_start) { return $date; @@ -137,11 +138,11 @@ class CAbsoluteTimeParser extends CParser { } if (!array_key_exists('i', $this->tokens)) { - return new DateTime($date->format('Y-m-d H:59:59')); + return new DateTime($date->format('Y-m-d H:59:59'), $timezone); } if (!array_key_exists('s', $this->tokens)) { - return new DateTime($date->format('Y-m-d H:i:59')); + return new DateTime($date->format('Y-m-d H:i:59'), $timezone); } return $date; diff --git a/ui/include/classes/parsers/CValidationRule.php b/ui/include/classes/parsers/CValidationRule.php index 313d0287899..b0e9f9ad4d2 100644 --- a/ui/include/classes/parsers/CValidationRule.php +++ b/ui/include/classes/parsers/CValidationRule.php @@ -42,7 +42,6 @@ class CValidationRule { $pos = 0; $state = self::STATE_BEGIN; $rules = []; - $is_empty = true; while (isset($buffer[$pos])) { switch ($state) { @@ -53,28 +52,30 @@ class CValidationRule { break; default: - $is_empty = false; $rule = []; - if (!$this->parseString($buffer, $pos, $rule) // string - && !$this->parseRangeTime($buffer, $pos, $rule) // range time - && !$this->parseTimePeriods($buffer, $pos, $rule) // time periods - && !$this->parseTimeUnit($buffer, $pos, $rule) // time unit - && !$this->parseRgb($buffer, $pos, $rule) // rgb - && !$this->parseRequired($buffer, $pos, $rule) // required - && !$this->parseNotEmpty($buffer, $pos, $rule) // not_empty - && !$this->parseLE($buffer, $pos, $rule) // le - && !$this->parseJson($buffer, $pos, $rule) // json - && !$this->parseInt32($buffer, $pos, $rule) // int32 - && !$this->parseIn($buffer, $pos, $rule) // in - && !$this->parseId($buffer, $pos, $rule) // id - && !$this->parseGE($buffer, $pos, $rule) // ge - && !$this->parseFatal($buffer, $pos, $rule) // fatal - && !$this->parseDB($buffer, $pos, $rule) // db - && !$this->parseArrayId($buffer, $pos, $rule) // array_id - && !$this->parseArrayDB($buffer, $pos, $rule) // array_db - && !$this->parseArray($buffer, $pos, $rule) // array - && !$this->parseFlags($buffer, $pos, $rule)) { // flags + if (!$this->parseString($buffer, $pos, $rule) + && !$this->parseRangeTime($buffer, $pos, $rule) + && !$this->parseAbsDate($buffer, $pos, $rule) + && !$this->parseAbsTime($buffer, $pos, $rule) + && !$this->parseTimePeriods($buffer, $pos, $rule) + && !$this->parseTimeUnit($buffer, $pos, $rule) + && !$this->parseRgb($buffer, $pos, $rule) + && !$this->parseRequired($buffer, $pos, $rule) + && !$this->parseNotEmpty($buffer, $pos, $rule) + && !$this->parseLE($buffer, $pos, $rule) + && !$this->parseJson($buffer, $pos, $rule) + && !$this->parseInt32($buffer, $pos, $rule) + && !$this->parseUInt64($buffer, $pos, $rule) + && !$this->parseIn($buffer, $pos, $rule) + && !$this->parseId($buffer, $pos, $rule) + && !$this->parseGE($buffer, $pos, $rule) + && !$this->parseFatal($buffer, $pos, $rule) + && !$this->parseDB($buffer, $pos, $rule) + && !$this->parseArrayId($buffer, $pos, $rule) + && !$this->parseArrayDB($buffer, $pos, $rule) + && !$this->parseArray($buffer, $pos, $rule) + && !$this->parseFlags($buffer, $pos, $rule)) { // incorrect validation rule break 3; } @@ -174,6 +175,38 @@ class CValidationRule { return true; } + /** + * abs_date + * + * 'abs_date' => true + */ + private function parseAbsDate($buffer, &$pos, &$rules) { + if (strncmp(substr($buffer, $pos), 'abs_date', 8) != 0) { + return false; + } + + $pos += 8; + $rules['abs_date'] = true; + + return true; + } + + /** + * abs_time + * + * 'abs_time' => true + */ + private function parseAbsTime($buffer, &$pos, &$rules) { + if (strncmp(substr($buffer, $pos), 'abs_time', 8) != 0) { + return false; + } + + $pos += 8; + $rules['abs_time'] = true; + + return true; + } + /** * range_time * @@ -355,6 +388,22 @@ class CValidationRule { return true; } + /** + * uint64 + * + * 'uint64' => true + */ + private function parseUInt64($buffer, &$pos, &$rules) { + if (strncmp(substr($buffer, $pos), 'uint64', 6) != 0) { + return false; + } + + $pos += 6; + $rules['uint64'] = true; + + return true; + } + /** * in [,...,] * diff --git a/ui/include/classes/validators/CNewValidator.php b/ui/include/classes/validators/CNewValidator.php index 7517951e04e..31324d0e9c2 100644 --- a/ui/include/classes/validators/CNewValidator.php +++ b/ui/include/classes/validators/CNewValidator.php @@ -140,6 +140,20 @@ class CNewValidator { } break; + case 'uint64': + if (array_key_exists($field, $this->input)) { + if (!is_string($this->input[$field]) || !self::is_uint64($this->input[$field])) { + $this->addError($fatal, + is_scalar($this->input[$field]) + ? _s('Incorrect value "%1$s" for "%2$s" field.', $this->input[$field], $field) + : _s('Incorrect value for "%1$s" field.', $field) + ); + + return false; + } + } + break; + case 'id': if (array_key_exists($field, $this->input)) { if (!is_string($this->input[$field]) || !self::is_id($this->input[$field])) { @@ -276,6 +290,41 @@ class CNewValidator { } break; + case 'abs_date': + if (array_key_exists($field, $this->input)) { + $absolute_time_parser = new CAbsoluteTimeParser(); + + $has_errors = !is_string($this->input[$field]) + || $absolute_time_parser->parse($this->input[$field]) != CParser::PARSE_SUCCESS + || $absolute_time_parser->getDateTime(true)->format('H:i:s') !== '00:00:00'; + + if ($has_errors) { + $this->addError($fatal, + _s('Incorrect value for field "%1$s": %2$s.', $field, _('a date is expected')) + ); + + return false; + } + } + break; + + case 'abs_time': + if (array_key_exists($field, $this->input)) { + $absolute_time_parser = new CAbsoluteTimeParser(); + + $has_errors = !is_string($this->input[$field]) + || $absolute_time_parser->parse($this->input[$field]) != CParser::PARSE_SUCCESS; + + if ($has_errors) { + $this->addError($fatal, + _s('Incorrect value for field "%1$s": %2$s.', $field, _('a time is expected')) + ); + + return false; + } + } + break; + /* * 'time_periods' => true */ diff --git a/ui/tests/unit/include/classes/parsers/CAbsoluteTimeParserTest.php b/ui/tests/unit/include/classes/parsers/CAbsoluteTimeParserTest.php index 25849a80eaa..82966eed277 100644 --- a/ui/tests/unit/include/classes/parsers/CAbsoluteTimeParserTest.php +++ b/ui/tests/unit/include/classes/parsers/CAbsoluteTimeParserTest.php @@ -156,6 +156,14 @@ class CAbsoluteTimeParserTest extends TestCase { ], 'datetime' => ['values' => ['2018-04-01 00:00:00', '2018-04-30 23:59:59']] ], + [ + '2018-04-15 12:45', 0, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '2018-04-15 12:45' + ], + 'datetime' => ['values' => ['2018-04-15 12:45:00', '2018-04-15 12:45:59'], 'tz' => 'UTC'] + ], [ 'text', 0, [ @@ -256,9 +264,14 @@ class CAbsoluteTimeParserTest extends TestCase { ]); $this->assertSame(strlen($expected['match']), $parser->getLength()); - $ts_from = $parser->getDateTime(true); - $ts_to = $parser->getDateTime(false); + $tz = array_key_exists('tz', $datetime) ? new DateTimeZone($datetime['tz']) : null; + $ts_from = $parser->getDateTime(true, $tz); + $ts_to = $parser->getDateTime(false, $tz); $this->assertSame($datetime['values'][0], $ts_from !== null ? $ts_from->format('Y-m-d H:i:s') : null); $this->assertSame($datetime['values'][1], $ts_to !== null ? $ts_to->format('Y-m-d H:i:s') : null); + if ($tz !== null) { + $this->assertSame($datetime['tz'], $ts_from->getTimeZone()->getName()); + $this->assertSame($datetime['tz'], $ts_to->getTimeZone()->getName()); + } } } diff --git a/ui/tests/unit/include/classes/parsers/CValidationRuleTest.php b/ui/tests/unit/include/classes/parsers/CValidationRuleTest.php index 79ce9115604..350e5dfc82f 100644 --- a/ui/tests/unit/include/classes/parsers/CValidationRuleTest.php +++ b/ui/tests/unit/include/classes/parsers/CValidationRuleTest.php @@ -65,6 +65,11 @@ class CValidationRuleTest extends TestCase { 'int32' => true ] ], + ['uint64', '', + [ + 'uint64' => true + ] + ], ['db hosts.name', '', [ 'db' => [ @@ -141,6 +146,16 @@ class CValidationRuleTest extends TestCase { 'range_time' => true ] ], + ['abs_date', '', + [ + 'abs_date' => true + ] + ], + ['abs_time', '', + [ + 'abs_time' => true + ] + ], ['time_unit', '', [ 'time_unit' => [] -- cgit v1.2.3