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
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();
|