1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22require_once __DIR__.'/include/config.inc.php';
23
24$config = select_config();
25$redirect_to = (new CUrl('index.php'))->setArgument('form', 'default');
26
27$request = CSession::getValue('request');
28CSession::unsetValue(['request']);
29
30if (hasRequest('request')) {
31	$request = getRequest('request');
32	preg_match('/^\/?(?<filename>[a-z0-9_.]+\.php)(\?.*)?$/i', $request, $test_request);
33
34	if (!array_key_exists('filename', $test_request) || !file_exists('./'.$test_request['filename'])
35			|| $test_request['filename'] === basename(__FILE__)) {
36		$request = '';
37	}
38
39	if ($request !== '') {
40		$redirect_to->setArgument('request', $request);
41		CSession::setValue('request', $request);
42	}
43}
44
45if ($config['saml_auth_enabled'] == ZBX_AUTH_SAML_DISABLED) {
46	CSession::unsetValue(['request']);
47
48	redirect($redirect_to->toString());
49}
50
51require_once __DIR__.'/vendor/php-saml/_toolkit_loader.php';
52require_once __DIR__.'/vendor/xmlseclibs/xmlseclibs.php';
53
54use OneLogin\Saml2\Auth;
55use OneLogin\Saml2\Utils;
56
57global $SSO;
58
59$sp_key = '';
60$sp_cert = '';
61$idp_cert = '';
62
63if (is_array($SSO) && array_key_exists('SP_KEY', $SSO)) {
64	if (is_readable($SSO['SP_KEY'])) {
65		$sp_key = file_get_contents($SSO['SP_KEY']);
66	}
67}
68elseif (is_readable('conf/certs/sp.key')) {
69	$sp_key = file_get_contents('conf/certs/sp.key');
70}
71
72if (is_array($SSO) && array_key_exists('SP_CERT', $SSO)) {
73	if (is_readable($SSO['SP_CERT'])) {
74		$sp_cert = file_get_contents($SSO['SP_CERT']);
75	}
76}
77elseif (is_readable('conf/certs/sp.crt')) {
78	$sp_cert = file_get_contents('conf/certs/sp.crt');
79}
80
81if (is_array($SSO) && array_key_exists('IDP_CERT', $SSO)) {
82	if (is_readable($SSO['IDP_CERT'])) {
83		$idp_cert = file_get_contents($SSO['IDP_CERT']);
84	}
85}
86elseif (is_readable('conf/certs/idp.crt')) {
87	$idp_cert = file_get_contents('conf/certs/idp.crt');
88}
89
90if (is_array($SSO) && array_key_exists('SETTINGS', $SSO)) {
91	if (array_key_exists('baseurl', $SSO['SETTINGS']) && !is_array($SSO['SETTINGS']['baseurl'])
92			&& $SSO['SETTINGS']['baseurl'] !== '') {
93		Utils::setBaseURL((string) $SSO['SETTINGS']['baseurl']);
94	}
95
96	if (array_key_exists('use_proxy_headers', $SSO['SETTINGS']) && (bool) $SSO['SETTINGS']['use_proxy_headers']) {
97		Utils::setProxyVars(true);
98	}
99}
100
101$baseurl = Utils::getSelfURLNoQuery();
102$relay_state = null;
103$settings = [
104	'sp' => [
105		'entityId' => $config['saml_sp_entityid'],
106		'assertionConsumerService' => [
107			'url' => $baseurl.'?acs'
108		],
109		'singleLogoutService' => [
110			'url' => $baseurl.'?sls'
111		],
112		'NameIDFormat' => $config['saml_nameid_format'],
113		'x509cert' => $sp_cert,
114		'privateKey' => $sp_key
115	],
116	'idp' => [
117		'entityId' => $config['saml_idp_entityid'],
118		'singleSignOnService' => [
119			'url' => $config['saml_sso_url']
120		],
121		'singleLogoutService' => [
122			'url' => $config['saml_slo_url']
123		],
124		'x509cert' => $idp_cert
125	],
126	'security' => [
127		'nameIdEncrypted' => (bool) $config['saml_encrypt_nameid'],
128		'authnRequestsSigned' => (bool) $config['saml_sign_authn_requests'],
129		'logoutRequestSigned' => (bool) $config['saml_sign_logout_requests'],
130		'logoutResponseSigned' => (bool) $config['saml_sign_logout_responses'],
131		'wantMessagesSigned' => (bool) $config['saml_sign_messages'],
132		'wantAssertionsEncrypted' => (bool) $config['saml_encrypt_assertions'],
133		'wantAssertionsSigned' => (bool) $config['saml_sign_assertions'],
134		'wantNameIdEncrypted' => (bool) $config['saml_encrypt_nameid']
135	]
136];
137
138if (is_array($SSO) && array_key_exists('SETTINGS', $SSO)) {
139	foreach (['strict', 'compress', 'contactPerson', 'organization'] as $option) {
140		if (array_key_exists($option, $SSO['SETTINGS'])) {
141			$settings[$option] = $SSO['SETTINGS'][$option];
142		}
143	}
144
145	if (array_key_exists('sp', $SSO['SETTINGS'])) {
146		foreach (['attributeConsumingService', 'x509certNew'] as $option) {
147			if (array_key_exists($option, $SSO['SETTINGS']['sp'])) {
148				$settings['sp'][$option] = $SSO['SETTINGS']['sp'][$option];
149			}
150		}
151	}
152
153	if (array_key_exists('idp', $SSO['SETTINGS'])) {
154		if (array_key_exists('singleLogoutService', $SSO['SETTINGS']['idp'])
155				&& array_key_exists('responseUrl', $SSO['SETTINGS']['idp']['singleLogoutService'])) {
156			$settings['idp']['singleLogoutService']['responseUrl'] =
157				$SSO['SETTINGS']['idp']['singleLogoutService']['responseUrl'];
158		}
159
160		foreach (['certFingerprint', 'certFingerprintAlgorithm', 'x509certMulti'] as $option) {
161			if (array_key_exists($option, $SSO['SETTINGS']['idp'])) {
162				$settings['idp'][$option] = $SSO['SETTINGS']['idp'][$option];
163			}
164		}
165	}
166
167	if (array_key_exists('security', $SSO['SETTINGS'])) {
168		foreach (['signMetadata', 'wantNameId', 'requestedAuthnContext', 'requestedAuthnContextComparison',
169				'wantXMLValidation', 'relaxDestinationValidation', 'destinationStrictlyMatches', 'lowercaseUrlencoding',
170				'rejectUnsolicitedResponsesWithInResponseTo', 'signatureAlgorithm', 'digestAlgorithm'] as $option) {
171			if (array_key_exists($option, $SSO['SETTINGS']['security'])) {
172				$settings['security'][$option] = $SSO['SETTINGS']['security'][$option];
173			}
174		}
175	}
176}
177
178try {
179	$auth = new Auth($settings);
180
181	if (hasRequest('acs') && !CSession::keyExists('saml_data')) {
182		$auth->processResponse();
183
184		if (!$auth->isAuthenticated()) {
185			throw new Exception($auth->getLastErrorReason());
186		}
187
188		$user_attributes = $auth->getAttributes();
189
190		if (!array_key_exists($config['saml_username_attribute'], $user_attributes)) {
191			throw new Exception(
192				_s('The parameter "%1$s" is missing from the user attributes.', $config['saml_username_attribute'])
193			);
194		}
195
196		CSession::setValue('saml_data', [
197			'username_attribute' => reset($user_attributes[$config['saml_username_attribute']]),
198			'nameid' => $auth->getNameId(),
199			'nameid_format' => $auth->getNameIdFormat(),
200			'nameid_name_qualifier' => $auth->getNameIdNameQualifier(),
201			'nameid_sp_name_qualifier' => $auth->getNameIdSPNameQualifier(),
202			'session_index' => $auth->getSessionIndex()
203		]);
204
205		if (hasRequest('RelayState') && strpos(getRequest('RelayState'), $baseurl) === false) {
206			$relay_state = getRequest('RelayState');
207		}
208	}
209
210	if ($config['saml_slo_url'] !== '') {
211		if (hasRequest('slo') && CSession::keyExists('saml_data')) {
212			$saml_data = CSession::getValue('saml_data');
213
214			CWebUser::logout();
215
216			$auth->logout(null, [], $saml_data['nameid'], $saml_data['session_index'], false,
217				$saml_data['nameid_format'], $saml_data['nameid_name_qualifier'], $saml_data['nameid_sp_name_qualifier']
218			);
219		}
220
221		if (hasRequest('sls')) {
222			$auth->processSLO();
223
224			redirect('index.php');
225		}
226	}
227
228	if (CWebUser::isLoggedIn() && !CWebUser::isGuest()) {
229		redirect($redirect_to->toString());
230	}
231
232	if (CSession::keyExists('saml_data')) {
233		$saml_data = CSession::getValue('saml_data');
234		$user = API::getApiService('user')->loginByAlias($saml_data['username_attribute'],
235			($config['saml_case_sensitive'] == ZBX_AUTH_CASE_SENSITIVE), $config['authentication_type']
236		);
237
238		if ($user['gui_access'] == GROUP_GUI_ACCESS_DISABLED) {
239			CSession::unsetValue(['saml_data']);
240
241			throw new Exception(_('GUI access disabled.'));
242		}
243
244		CWebUser::setSessionCookie($user['sessionid']);
245
246		$redirect = array_filter([$request, $user['url'], $relay_state, ZBX_DEFAULT_URL]);
247		redirect(reset($redirect));
248	}
249
250	$auth->login();
251}
252catch (Exception $e) {
253	error($e->getMessage());
254}
255
256echo (new CView('general.warning', [
257	'header' => _('You are not logged in'),
258	'messages' => array_column(clear_messages(), 'message'),
259	'buttons' => [
260		(new CButton('login', _('Login')))->onClick(
261			'document.location = '.json_encode(
262				$redirect_to
263					->setArgument('request', $request)
264					->getUrl()
265			).';'
266		)
267	],
268	'theme' => getUserTheme(CWebUser::$data)
269]))->getOutput();
270