<?php declare(strict_types = 0); /* ** 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 Affero General Public License as published by the Free Software Foundation, version 3. ** ** 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ /** * Class containing methods for operations with authentication parameters. */ class CAuthentication extends CApiService { public const ACCESS_RULES = [ 'get' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 'update' => ['min_user_type' => USER_TYPE_SUPER_ADMIN] ]; /** * @param array $options * * @throws APIException if the input is invalid. * * @return array */ public function get(array $options): array { $output_fields = self::getOutputFields(); $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'output' => ['type' => API_OUTPUT, 'in' => implode(',', $output_fields), 'default' => API_OUTPUT_EXTEND] ]]; if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } if ($options['output'] === API_OUTPUT_EXTEND) { $options['output'] = $output_fields; } return CApiSettingsHelper::getParameters($options['output']); } /** * Get the fields of the Authentication API object that are used by parts of the UI where authentication is not * required. */ public static function getPublic(): array { global $ALLOW_HTTP_AUTH; return ($ALLOW_HTTP_AUTH ? [] : ['http_auth_enabled' => ZBX_AUTH_HTTP_DISABLED]) + CApiSettingsHelper::getParameters([ 'authentication_type', 'http_auth_enabled', 'http_login_form', 'http_strip_domains', 'http_case_sensitive', 'saml_auth_enabled', 'saml_case_sensitive', 'saml_jit_status', 'disabled_usrgrpid', 'mfa_status', 'mfaid', 'ldap_userdirectoryid' ]); } /** * @param array $auth * * @throws APIException if the input is invalid. * * @return array */ public function update(array $auth): array { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _s('No permissions to call "%1$s.%2$s".', 'authentication', __FUNCTION__) ); } $this->validateUpdate($auth, $db_auth); CApiSettingsHelper::updateParameters($auth, $db_auth); self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_AUTHENTICATION, [$auth], [$db_auth]); return array_keys($auth); } /** * @param array $auth * @param array|null $db_auth * * @throws APIException */ protected function validateUpdate(array $auth, ?array &$db_auth): void { global $ALLOW_HTTP_AUTH; $auth += CApiSettingsHelper::getParameters(['authentication_type']); $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'authentication_type' => ['type' => API_INT32, 'in' => ZBX_AUTH_INTERNAL.','.ZBX_AUTH_LDAP], 'ldap_auth_enabled' => ['type' => API_MULTIPLE, 'rules' => [ ['if' => ['field' => 'authentication_type', 'in' => ZBX_AUTH_LDAP], 'type' => API_INT32, 'in' => ZBX_AUTH_LDAP_ENABLED], ['else' => true, 'type' => API_INT32, 'in' => ZBX_AUTH_LDAP_DISABLED.','.ZBX_AUTH_LDAP_ENABLED] ]], 'ldap_case_sensitive' => ['type' => API_INT32, 'in' => ZBX_AUTH_CASE_INSENSITIVE.','.ZBX_AUTH_CASE_SENSITIVE], 'ldap_userdirectoryid' => ['type' => API_ID], 'saml_auth_enabled' => ['type' => API_INT32, 'in' => ZBX_AUTH_SAML_DISABLED.','.ZBX_AUTH_SAML_ENABLED], 'saml_case_sensitive' => ['type' => API_INT32, 'in' => ZBX_AUTH_CASE_INSENSITIVE.','.ZBX_AUTH_CASE_SENSITIVE], 'passwd_min_length' => ['type' => API_INT32, 'in' => '1:70'], 'passwd_check_rules' => ['type' => API_INT32, 'in' => '0:'.(PASSWD_CHECK_CASE | PASSWD_CHECK_DIGITS | PASSWD_CHECK_SPECIAL | PASSWD_CHECK_SIMPLE)], 'disabled_usrgrpid' => ['type' => API_ID], 'jit_provision_interval' => ['type' => API_TIME_UNIT, 'flags' => API_TIME_UNIT_WITH_YEAR, 'in' => implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR])], 'saml_jit_status' => ['type' => API_INT32, 'in' => implode(',', [JIT_PROVISIONING_DISABLED, JIT_PROVISIONING_ENABLED])], 'ldap_jit_status' => ['type' => API_INT32, 'in' => implode(',', [JIT_PROVISIONING_DISABLED, JIT_PROVISIONING_ENABLED])], 'mfa_status' => ['type' => API_INT32, 'in' => implode(',', [MFA_DISABLED, MFA_ENABLED])], 'mfaid' => ['type' => API_ID] ]]; if ($ALLOW_HTTP_AUTH) { $api_input_rules['fields'] += [ 'http_auth_enabled' => ['type' => API_INT32, 'in' => ZBX_AUTH_HTTP_DISABLED.','.ZBX_AUTH_HTTP_ENABLED], 'http_login_form' => ['type' => API_INT32, 'in' => ZBX_AUTH_FORM_ZABBIX.','.ZBX_AUTH_FORM_HTTP], 'http_strip_domains' => ['type' => API_STRING_UTF8, 'length' => CSettingsSchema::getFieldLength('http_strip_domains')], 'http_case_sensitive' => ['type' => API_INT32, 'in' => ZBX_AUTH_CASE_INSENSITIVE.','.ZBX_AUTH_CASE_SENSITIVE] ]; } if (!CApiInputValidator::validate($api_input_rules, $auth, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_auth = CApiSettingsHelper::getParameters(self::getOutputFields(), false); CApiSettingsHelper::checkUndeclaredParameters($auth, $db_auth); $auth += array_intersect_key($db_auth, array_flip(['ldap_auth_enabled', 'ldap_userdirectoryid', 'disabled_usrgrpid', 'saml_auth_enabled', 'saml_jit_status', 'ldap_jit_status', 'mfa_status', 'mfaid' ])); self::checkUserDirectoryid($auth, $db_auth); self::checkDeprovisionedUsersGroup($auth); self::checkMfaExists($auth); self::checkMfaid($auth, $db_auth); } private static function checkUserDirectoryid(array $auth, array $db_auth): void { if ($auth['ldap_userdirectoryid'] != 0) { if (bccomp($auth['ldap_userdirectoryid'], $db_auth['ldap_userdirectoryid']) != 0) { $default_ldap_exists = API::UserDirectory()->get([ 'output' => [], 'userdirectoryids' => [$auth['ldap_userdirectoryid']], 'filter' => ['idp_type' => IDP_TYPE_LDAP] ]); if (!$default_ldap_exists) { static::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/ldap_userdirectoryid', _('object does not exist')) ); } } } elseif ($auth['ldap_auth_enabled'] == ZBX_AUTH_LDAP_ENABLED) { self::exception(ZBX_API_ERROR_PARAMETERS, _('Default LDAP server must be specified.')); } } private static function checkDeprovisionedUsersGroup(array $auth): void { if ($auth['disabled_usrgrpid'] != 0) { $groups = API::UserGroup()->get([ 'output' => ['users_status'], 'usrgrpids' => [$auth['disabled_usrgrpid']] ]); if (!$groups) { static::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } if ($groups[0]['users_status'] != GROUP_STATUS_DISABLED) { static::exception(ZBX_API_ERROR_PARAMETERS, _('Deprovisioned users group cannot be enabled.')); } } else { $ldap_jit_enabled = $auth['ldap_auth_enabled'] == ZBX_AUTH_LDAP_ENABLED && $auth['ldap_jit_status'] == JIT_PROVISIONING_ENABLED; $saml_jit_enabled = $auth['saml_auth_enabled'] == ZBX_AUTH_SAML_ENABLED && $auth['saml_jit_status'] == JIT_PROVISIONING_ENABLED; if ($ldap_jit_enabled || $saml_jit_enabled) { static::exception(ZBX_API_ERROR_PARAMETERS, _('Deprovisioned users group cannot be empty.')); } } } private static function checkMfaExists(array $auth): void { if ($auth['mfa_status'] == MFA_ENABLED) { $mfa_count = DB::select('mfa', ['countOutput' => true]); if ($mfa_count == 0) { static::exception(ZBX_API_ERROR_PARAMETERS, _('At least one MFA method must exist.')); } } } private static function checkMfaid(array $auth, array $db_auth): void { $mfaid_changed = bccomp($auth['mfaid'], $db_auth['mfaid']) != 0; if (($auth['mfa_status'] == MFA_DISABLED && $auth['mfaid'] != 0 && $mfaid_changed) || ($auth['mfa_status'] == MFA_ENABLED && ($mfaid_changed || $auth['mfaid'] == 0))) { $db_mfas = DB::select('mfa', [ 'output' => ['mfaid'], 'mfaids' => $auth['mfaid'] ]); if (!$db_mfas) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/mfaid', _('object does not exist') )); } } } /** * Gets output fields based on if HTTP authentication support is enabled or not. * * @return array */ public static function getOutputFields(): array { global $ALLOW_HTTP_AUTH; $output_fields = ['authentication_type', 'ldap_auth_enabled', 'ldap_case_sensitive', 'ldap_userdirectoryid', 'saml_auth_enabled', 'saml_case_sensitive', 'passwd_min_length', 'passwd_check_rules', 'disabled_usrgrpid', 'jit_provision_interval', 'saml_jit_status', 'ldap_jit_status', 'mfa_status', 'mfaid' ]; $http_output_fields = ['http_auth_enabled', 'http_login_form', 'http_strip_domains', 'http_case_sensitive']; return $ALLOW_HTTP_AUTH ? array_merge($output_fields, $http_output_fields) : $output_fields; } }