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$redirect_to = (new CUrl('index.php'))->setArgument('form', 'default');
25
26$request = CSessionHelper::get('request');
27CSessionHelper::unset(['request']);
28
29if (hasRequest('request')) {
30	$request = getRequest('request');
31	preg_match('/^\/?(?<filename>[a-z0-9_.]+\.php)(\?.*)?$/i', $request, $test_request);
32
33	if (!array_key_exists('filename', $test_request) || !file_exists('./'.$test_request['filename'])
34			|| $test_request['filename'] === basename(__FILE__)) {
35		$request = '';
36	}
37
38	if ($request !== '') {
39		$redirect_to->setArgument('request', $request);
40		CSessionHelper::set('request', $request);
41	}
42}
43
44if (CAuthenticationHelper::get(CAuthenticationHelper::SAML_AUTH_ENABLED) == ZBX_AUTH_SAML_DISABLED) {
45	CSessionHelper::unset(['request']);
46
47	redirect($redirect_to->toString());
48}
49
50require_once __DIR__.'/vendor/php-saml/_toolkit_loader.php';
51require_once __DIR__.'/vendor/xmlseclibs/xmlseclibs.php';
52
53use OneLogin\Saml2\Auth;
54use OneLogin\Saml2\Utils;
55
56global $SSO;
57
58$sp_key = '';
59$sp_cert = '';
60$idp_cert = '';
61
62if (is_array($SSO) && array_key_exists('SP_KEY', $SSO)) {
63	if (is_readable($SSO['SP_KEY'])) {
64		$sp_key = file_get_contents($SSO['SP_KEY']);
65	}
66}
67elseif (is_readable('conf/certs/sp.key')) {
68	$sp_key = file_get_contents('conf/certs/sp.key');
69}
70
71if (is_array($SSO) && array_key_exists('SP_CERT', $SSO)) {
72	if (is_readable($SSO['SP_CERT'])) {
73		$sp_cert = file_get_contents($SSO['SP_CERT']);
74	}
75}
76elseif (is_readable('conf/certs/sp.crt')) {
77	$sp_cert = file_get_contents('conf/certs/sp.crt');
78}
79
80if (is_array($SSO) && array_key_exists('IDP_CERT', $SSO)) {
81	if (is_readable($SSO['IDP_CERT'])) {
82		$idp_cert = file_get_contents($SSO['IDP_CERT']);
83	}
84}
85elseif (is_readable('conf/certs/idp.crt')) {
86	$idp_cert = file_get_contents('conf/certs/idp.crt');
87}
88
89if (is_array($SSO) && array_key_exists('SETTINGS', $SSO)) {
90	if (array_key_exists('baseurl', $SSO['SETTINGS']) && !is_array($SSO['SETTINGS']['baseurl'])
91			&& $SSO['SETTINGS']['baseurl'] !== '') {
92		Utils::setBaseURL((string) $SSO['SETTINGS']['baseurl']);
93	}
94
95	if (array_key_exists('use_proxy_headers', $SSO['SETTINGS']) && (bool) $SSO['SETTINGS']['use_proxy_headers']) {
96		Utils::setProxyVars(true);
97	}
98}
99
100$baseurl = Utils::getSelfURLNoQuery();
101$relay_state = null;
102$settings = [
103	'sp' => [
104		'entityId' => CAuthenticationHelper::get(CAuthenticationHelper::SAML_SP_ENTITYID),
105		'assertionConsumerService' => [
106			'url' => $baseurl.'?acs'
107		],
108		'singleLogoutService' => [
109			'url' => $baseurl.'?sls'
110		],
111		'NameIDFormat' => CAuthenticationHelper::get(CAuthenticationHelper::SAML_NAMEID_FORMAT),
112		'x509cert' => $sp_cert,
113		'privateKey' => $sp_key
114	],
115	'idp' => [
116		'entityId' => CAuthenticationHelper::get(CAuthenticationHelper::SAML_IDP_ENTITYID),
117		'singleSignOnService' => [
118			'url' => CAuthenticationHelper::get(CAuthenticationHelper::SAML_SSO_URL)
119		],
120		'singleLogoutService' => [
121			'url' => CAuthenticationHelper::get(CAuthenticationHelper::SAML_SLO_URL)
122		],
123		'x509cert' => $idp_cert
124	],
125	'security' => [
126		'nameIdEncrypted' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_ENCRYPT_NAMEID),
127		'authnRequestsSigned' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_SIGN_AUTHN_REQUESTS),
128		'logoutRequestSigned' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_SIGN_LOGOUT_REQUESTS),
129		'logoutResponseSigned' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_SIGN_LOGOUT_RESPONSES),
130		'wantMessagesSigned' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_SIGN_MESSAGES),
131		'wantAssertionsEncrypted' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_ENCRYPT_ASSERTIONS),
132		'wantAssertionsSigned' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_SIGN_ASSERTIONS),
133		'wantNameIdEncrypted' => (bool) CAuthenticationHelper::get(CAuthenticationHelper::SAML_ENCRYPT_NAMEID)
134	]
135];
136
137if (is_array($SSO) && array_key_exists('SETTINGS', $SSO)) {
138	foreach (['strict', 'compress', 'contactPerson', 'organization'] as $option) {
139		if (array_key_exists($option, $SSO['SETTINGS'])) {
140			$settings[$option] = $SSO['SETTINGS'][$option];
141		}
142	}
143
144	if (array_key_exists('sp', $SSO['SETTINGS'])) {
145		foreach (['attributeConsumingService', 'x509certNew'] as $option) {
146			if (array_key_exists($option, $SSO['SETTINGS']['sp'])) {
147				$settings['sp'][$option] = $SSO['SETTINGS']['sp'][$option];
148			}
149		}
150	}
151
152	if (array_key_exists('idp', $SSO['SETTINGS'])) {
153		if (array_key_exists('singleLogoutService', $SSO['SETTINGS']['idp'])
154				&& array_key_exists('responseUrl', $SSO['SETTINGS']['idp']['singleLogoutService'])) {
155			$settings['idp']['singleLogoutService']['responseUrl'] =
156				$SSO['SETTINGS']['idp']['singleLogoutService']['responseUrl'];
157		}
158
159		foreach (['certFingerprint', 'certFingerprintAlgorithm', 'x509certMulti'] as $option) {
160			if (array_key_exists($option, $SSO['SETTINGS']['idp'])) {
161				$settings['idp'][$option] = $SSO['SETTINGS']['idp'][$option];
162			}
163		}
164	}
165
166	if (array_key_exists('security', $SSO['SETTINGS'])) {
167		foreach (['signMetadata', 'wantNameId', 'requestedAuthnContext', 'requestedAuthnContextComparison',
168				'wantXMLValidation', 'relaxDestinationValidation', 'destinationStrictlyMatches', 'lowercaseUrlencoding',
169				'rejectUnsolicitedResponsesWithInResponseTo', 'signatureAlgorithm', 'digestAlgorithm'] as $option) {
170			if (array_key_exists($option, $SSO['SETTINGS']['security'])) {
171				$settings['security'][$option] = $SSO['SETTINGS']['security'][$option];
172			}
173		}
174	}
175}
176
177try {
178	$auth = new Auth($settings);
179
180	if (hasRequest('acs') && !CSessionHelper::has('saml_data')) {
181		$auth->processResponse();
182
183		if (!$auth->isAuthenticated()) {
184			throw new Exception($auth->getLastErrorReason());
185		}
186
187		$user_attributes = $auth->getAttributes();
188
189		if (!array_key_exists(CAuthenticationHelper::get(CAuthenticationHelper::SAML_USERNAME_ATTRIBUTE),
190			$user_attributes
191		)) {
192			throw new Exception(
193				_s('The parameter "%1$s" is missing from the user attributes.', CAuthenticationHelper::get(CAuthenticationHelper::SAML_USERNAME_ATTRIBUTE))
194			);
195		}
196
197		$saml_data = [
198			'username_attribute' => reset(
199				$user_attributes[CAuthenticationHelper::get(CAuthenticationHelper::SAML_USERNAME_ATTRIBUTE)]
200			),
201			'nameid' => $auth->getNameId(),
202			'nameid_format' => $auth->getNameIdFormat(),
203			'nameid_name_qualifier' => $auth->getNameIdNameQualifier(),
204			'nameid_sp_name_qualifier' => $auth->getNameIdSPNameQualifier(),
205			'session_index' => $auth->getSessionIndex()
206		];
207		$saml_data['sign'] = CEncryptHelper::sign(json_encode($saml_data));
208
209		CSessionHelper::set('saml_data', $saml_data);
210
211		if (hasRequest('RelayState') && strpos(getRequest('RelayState'), $baseurl) === false) {
212			$relay_state = getRequest('RelayState');
213		}
214	}
215
216	if (CAuthenticationHelper::get(CAuthenticationHelper::SAML_SLO_URL) !== '') {
217		if (hasRequest('slo') && CSessionHelper::has('saml_data')) {
218			$saml_data = CSessionHelper::get('saml_data');
219
220			CWebUser::logout();
221
222			$auth->logout(null, [], $saml_data['nameid'], $saml_data['session_index'], false,
223				$saml_data['nameid_format'], $saml_data['nameid_name_qualifier'], $saml_data['nameid_sp_name_qualifier']
224			);
225		}
226
227		if (hasRequest('sls')) {
228			$auth->processSLO();
229
230			redirect('index.php');
231		}
232	}
233
234	if (CWebUser::isLoggedIn() && !CWebUser::isGuest()) {
235		redirect($redirect_to->toString());
236	}
237
238	if (CSessionHelper::has('saml_data')) {
239		$saml_data = CSessionHelper::get('saml_data');
240
241		if (!array_key_exists('sign', $saml_data)) {
242			throw new Exception(_('Session initialization error.'));
243		}
244
245		$saml_data_sign = $saml_data['sign'];
246		$saml_data_sign_check = CEncryptHelper::sign(json_encode(array_diff_key($saml_data, array_flip(['sign']))));
247
248		if (!CEncryptHelper::checkSign($saml_data_sign, $saml_data_sign_check)) {
249			throw new Exception(_('Session initialization error.'));
250		}
251
252		CWebUser::$data = API::getApiService('user')->loginByUsername($saml_data['username_attribute'],
253			(CAuthenticationHelper::get(CAuthenticationHelper::SAML_CASE_SENSITIVE) == ZBX_AUTH_CASE_SENSITIVE),
254			CAuthenticationHelper::get(CAuthenticationHelper::AUTHENTICATION_TYPE)
255		);
256
257		if (CWebUser::$data['gui_access'] == GROUP_GUI_ACCESS_DISABLED) {
258			CSessionHelper::unset(['saml_data']);
259
260			throw new Exception(_('GUI access disabled.'));
261		}
262
263		CSessionHelper::set('sessionid', CWebUser::$data['sessionid']);
264		API::getWrapper()->auth = CWebUser::$data['sessionid'];
265
266		$redirect = array_filter([$request, CWebUser::$data['url'], $relay_state, CMenuHelper::getFirstUrl()]);
267		redirect(reset($redirect));
268	}
269
270	$auth->login();
271}
272catch (Exception $e) {
273	error($e->getMessage());
274}
275
276echo (new CView('general.warning', [
277	'header' => _('You are not logged in'),
278	'messages' => array_column(get_and_clear_messages(), 'message'),
279	'buttons' => [
280		(new CButton('login', _('Login')))->onClick(
281			'document.location = '.json_encode(
282				$redirect_to
283					->setArgument('request', $request)
284					->getUrl()
285			).';'
286		)
287	],
288	'theme' => getUserTheme(CWebUser::$data)
289]))->getOutput();
290
291session_write_close();
292