You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
12 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 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 __DIR__.'/include/config.inc.php';
$redirect_to = (new CUrl('index.php'))->setArgument('form', 'default');
$request = CSessionHelper::get('request');
CSessionHelper::unset(['request']);
if (hasRequest('request')) {
$request = getRequest('request');
preg_match('/^\/?(?<filename>[a-z0-9_.]+\.php)(\?.*)?$/i', $request, $test_request);
if (!array_key_exists('filename', $test_request) || !file_exists('./'.$test_request['filename'])
|| $test_request['filename'] === basename(__FILE__)) {
$request = '';
}
if ($request !== '') {
$redirect_to->setArgument('request', $request);
CSessionHelper::set('request', $request);
}
}
if (CAuthenticationHelper::get(CAuthenticationHelper::SAML_AUTH_ENABLED) == ZBX_AUTH_SAML_DISABLED) {
CSessionHelper::unset(['request']);
redirect($redirect_to->toString());
}
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Utils;
use SCIM\services\Group as ScimGroup;
global $SSO;
if (!is_array($SSO)) {
$SSO = [];
}
$SSO += ['SETTINGS' => []];
$certs = [
'SP_KEY' => 'conf/certs/sp.key',
'SP_CERT' => 'conf/certs/sp.crt',
'IDP_CERT' => 'conf/certs/idp.crt'
];
$certs = array_merge($certs, array_intersect_key($SSO, $certs));
$certs = array_filter($certs, 'is_readable');
$certs = array_map('file_get_contents', $certs);
$certs += array_fill_keys(['SP_KEY', 'SP_CERT', 'IDP_CERT'], '');
/** @var CUser $service */
$service = API::getApiService('user');
$userdirectoryid = CAuthenticationHelper::getSamlUserdirectoryid();
$provisioning = CProvisioning::forUserDirectoryId($userdirectoryid);
$provisioning_enabled = ($provisioning->isProvisioningEnabled()
&& CAuthenticationHelper::get(CAuthenticationHelper::SAML_JIT_STATUS) == JIT_PROVISIONING_ENABLED
);
if (array_key_exists('baseurl', $SSO['SETTINGS']) && !is_array($SSO['SETTINGS']['baseurl'])
&& $SSO['SETTINGS']['baseurl'] !== '') {
Utils::setBaseURL((string) $SSO['SETTINGS']['baseurl']);
}
if (array_key_exists('use_proxy_headers', $SSO['SETTINGS']) && (bool) $SSO['SETTINGS']['use_proxy_headers']) {
Utils::setProxyVars(true);
}
$baseurl = Utils::getSelfURLNoQuery();
$relay_state = null;
$saml_settings = $provisioning->getIdpConfig();
$settings = [
'sp' => [
'entityId' => $saml_settings['sp_entityid'],
'assertionConsumerService' => [
'url' => $baseurl.'?acs'
],
'singleLogoutService' => [
'url' => $baseurl.'?sls'
],
'NameIDFormat' => $saml_settings['nameid_format'],
'x509cert' => $certs['SP_CERT'],
'privateKey' => $certs['SP_KEY']
],
'idp' => [
'entityId' => $saml_settings['idp_entityid'],
'singleSignOnService' => [
'url' => $saml_settings['sso_url']
],
'singleLogoutService' => [
'url' => $saml_settings['slo_url']
],
'x509cert' => $certs['IDP_CERT']
],
'security' => [
'nameIdEncrypted' => (bool) $saml_settings['encrypt_nameid'],
'authnRequestsSigned' => (bool) $saml_settings['sign_authn_requests'],
'logoutRequestSigned' => (bool) $saml_settings['sign_logout_requests'],
'logoutResponseSigned' => (bool) $saml_settings['sign_logout_responses'],
'wantMessagesSigned' => (bool) $saml_settings['sign_messages'],
'wantAssertionsEncrypted' => (bool)$saml_settings['encrypt_assertions'],
'wantAssertionsSigned' => (bool) $saml_settings['sign_assertions'],
'wantNameIdEncrypted' => (bool) $saml_settings['encrypt_nameid']
]
];
foreach (['strict', 'compress', 'contactPerson', 'organization'] as $option) {
if (array_key_exists($option, $SSO['SETTINGS'])) {
$settings[$option] = $SSO['SETTINGS'][$option];
}
}
if (array_key_exists('sp', $SSO['SETTINGS'])) {
foreach (['attributeConsumingService', 'x509certNew'] as $option) {
if (array_key_exists($option, $SSO['SETTINGS']['sp'])) {
$settings['sp'][$option] = $SSO['SETTINGS']['sp'][$option];
}
}
}
if (array_key_exists('idp', $SSO['SETTINGS'])) {
if (array_key_exists('singleLogoutService', $SSO['SETTINGS']['idp'])
&& array_key_exists('responseUrl', $SSO['SETTINGS']['idp']['singleLogoutService'])) {
$settings['idp']['singleLogoutService']['responseUrl'] =
$SSO['SETTINGS']['idp']['singleLogoutService']['responseUrl'];
}
foreach (['certFingerprint', 'certFingerprintAlgorithm', 'x509certMulti'] as $option) {
if (array_key_exists($option, $SSO['SETTINGS']['idp'])) {
$settings['idp'][$option] = $SSO['SETTINGS']['idp'][$option];
}
}
}
if (array_key_exists('security', $SSO['SETTINGS'])) {
foreach (['signMetadata', 'wantNameId', 'requestedAuthnContext', 'requestedAuthnContextComparison',
'wantXMLValidation', 'relaxDestinationValidation', 'destinationStrictlyMatches', 'lowercaseUrlencoding',
'rejectUnsolicitedResponsesWithInResponseTo', 'signatureAlgorithm', 'digestAlgorithm'] as $option) {
if (array_key_exists($option, $SSO['SETTINGS']['security'])) {
$settings['security'][$option] = $SSO['SETTINGS']['security'][$option];
}
}
}
try {
CMessageHelper::clear();
$auth = new Auth($settings);
if (hasRequest('metadata')) {
$metadata = $auth->getSettings()->getSPMetadata();
header('Content-Type: text/xml');
echo $metadata;
session_write_close();
exit;
}
if (hasRequest('acs') && !CSessionHelper::has('saml_data')) {
$auth->processResponse();
if (!$auth->isAuthenticated()) {
throw new Exception($auth->getLastErrorReason());
}
$groups_key = $saml_settings['group_name'];
foreach ($auth->getAttributes() as $attribute => $value) {
if ($groups_key !== $attribute) {
$value = reset($value);
}
$user_attributes[$attribute] = $value;
}
if (!array_key_exists($saml_settings['username_attribute'], $user_attributes)) {
throw new Exception(
_s('The parameter "%1$s" is missing from the user attributes.', $saml_settings['username_attribute'])
);
}
$saml_data = [
'username_attribute' => $user_attributes[$saml_settings['username_attribute']],
'nameid' => $auth->getNameId(),
'nameid_format' => $auth->getNameIdFormat(),
'nameid_name_qualifier' => $auth->getNameIdNameQualifier(),
'nameid_sp_name_qualifier' => $auth->getNameIdSPNameQualifier(),
'session_index' => $auth->getSessionIndex(),
'provisioned_user' => []
];
if ($provisioning_enabled) {
$user = $provisioning->getUserAttributes($user_attributes);
$user['medias'] = $provisioning->getUserMedias($user_attributes);
$idp_groups = [];
if (array_key_exists($groups_key, $user_attributes) && is_array($user_attributes[$groups_key])) {
$idp_groups = (count($user_attributes[$groups_key]) > 1)
? $user_attributes[$groups_key]
: explode(';', $user_attributes[$groups_key][0]);
}
$user += $provisioning->getUserGroupsAndRole($idp_groups);
$saml_data['idp_groups'] = $idp_groups;
$saml_data['provisioned_user'] = $user;
}
$saml_data['sign'] = CEncryptHelper::sign(json_encode($saml_data));
CSessionHelper::set('saml_data', $saml_data);
if (hasRequest('RelayState') && strpos(getRequest('RelayState'), $baseurl) === false) {
$relay_state = getRequest('RelayState');
}
}
if ($saml_settings['slo_url'] !== '') {
if (hasRequest('slo') && CSessionHelper::has('saml_data')) {
$saml_data = CSessionHelper::get('saml_data');
CWebUser::logout();
$auth->logout(null, [], $saml_data['nameid'], $saml_data['session_index'], false,
$saml_data['nameid_format'], $saml_data['nameid_name_qualifier'], $saml_data['nameid_sp_name_qualifier']
);
}
if (hasRequest('sls')) {
CSessionHelper::unset(['saml_data']);
$auth->processSLO();
redirect('index.php');
}
}
if (CWebUser::isLoggedIn() && !CWebUser::isGuest()) {
redirect($redirect_to->toString());
}
if (CSessionHelper::has('saml_data')) {
$saml_data = CSessionHelper::get('saml_data');
if (!array_key_exists('sign', $saml_data)) {
throw new Exception(_('Session initialization error.'));
}
$saml_data_sign = $saml_data['sign'];
$saml_data_sign_check = CEncryptHelper::sign(json_encode(array_diff_key($saml_data, array_flip(['sign']))));
if (!CEncryptHelper::checkSign($saml_data_sign, $saml_data_sign_check)) {
throw new Exception(_('Session initialization error.'));
}
// Temporary disabling wrapper for API requests.
$wrapper = API::getWrapper();
API::setWrapper();
if ($saml_data['provisioned_user'] && $provisioning_enabled) {
$userdirectoryid = CAuthenticationHelper::getSamlUserdirectoryid();
$user_data = API::User()->findAccessibleUser($saml_data['username_attribute'],
(CAuthenticationHelper::get(CAuthenticationHelper::SAML_CASE_SENSITIVE) == ZBX_AUTH_CASE_SENSITIVE),
CAuthenticationHelper::get(CAuthenticationHelper::AUTHENTICATION_TYPE), false
);
if (array_key_exists('db_user', $user_data)) {
$deprovisioned = $user_data['permissions']['deprovisioned'];
if ($user_data['db_user']['userdirectoryid'] == $userdirectoryid) {
$saml_data['provisioned_user']['userid'] = $user_data['db_user']['userid'];
$deprovisioned = !API::User()->updateProvisionedUser($saml_data['provisioned_user']);
ScimGroup::createScimGroupsFromSamlAttributes($saml_data['idp_groups'],
$user_data['db_user']['userid']
);
}
if ($deprovisioned) {
throw new Exception(_('GUI access disabled.'));
}
}
elseif ($saml_data['provisioned_user']['roleid']) {
$saml_data['provisioned_user'] += [
'userdirectoryid' => $userdirectoryid,
'username' => $saml_data['username_attribute']
];
$user = API::User()->createProvisionedUser($saml_data['provisioned_user']);
ScimGroup::createScimGroupsFromSamlAttributes($saml_data['idp_groups'], $user['userid']);
}
unset($saml_data['provisioned_user'], $saml_data['idp_groups']);
CSessionHelper::set('saml_data', $saml_data);
}
CWebUser::$data = API::User()->loginByUsername($saml_data['username_attribute'],
(CAuthenticationHelper::get(CAuthenticationHelper::SAML_CASE_SENSITIVE) == ZBX_AUTH_CASE_SENSITIVE),
CAuthenticationHelper::get(CAuthenticationHelper::AUTHENTICATION_TYPE)
);
API::setWrapper($wrapper);
if (CWebUser::$data['gui_access'] == GROUP_GUI_ACCESS_DISABLED) {
throw new Exception(_('GUI access disabled.'));
}
CSessionHelper::set('sessionid', CWebUser::$data['sessionid']);
API::getWrapper()->auth = [
'type' => CJsonRpc::AUTH_TYPE_FRONTEND,
'auth' => CWebUser::$data['sessionid']
];
$redirect = array_filter([$request, CWebUser::$data['url'], $relay_state, CMenuHelper::getFirstUrl()]);
redirect(reset($redirect));
}
$auth->login();
}
catch (Exception $e) {
CSessionHelper::unset(['saml_data']);
error($e->getMessage());
}
echo (new CView('general.warning', [
'header' => _('You are not logged in'),
'messages' => array_column(get_and_clear_messages(), 'message'),
'buttons' => [
(new CButton('login', _('Login')))
->setAttribute('data-url',
$redirect_to
->setArgument('request', $request)
->getUrl()
)
->onClick('document.location = this.dataset.url;')
],
'theme' => getUserTheme(CWebUser::$data)
]))->getOutput();
session_write_close();