1<?php 2namespace LAM\AJAX; 3use htmlResponsiveTable; 4use htmlStatusMessage; 5use \LAM\TOOLS\IMPORT_EXPORT\Importer; 6use \LAM\TOOLS\IMPORT_EXPORT\Exporter; 7use \LAM\TYPES\TypeManager; 8use \htmlResponsiveRow; 9use \htmlLink; 10use \htmlOutputText; 11use \htmlButton; 12use \LAM\LOGIN\WEBAUTHN\WebauthnManager; 13use \LAMCfgMain; 14 15/* 16 17 This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) 18 Copyright (C) 2011 - 2020 Roland Gruber 19 20 This program is free software; you can redistribute it and/or modify 21 it under the terms of the GNU General Public License as published by 22 the Free Software Foundation; either version 2 of the License, or 23 (at your option) any later version. 24 25 This program is distributed in the hope that it will be useful, 26 but WITHOUT ANY WARRANTY; without even the implied warranty of 27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 GNU General Public License for more details. 29 30 You should have received a copy of the GNU General Public License 31 along with this program; if not, write to the Free Software 32 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 33 34*/ 35 36/** 37* Manages all AJAX requests. 38* 39* @author Roland Gruber 40* @package tools 41*/ 42 43/** security functions */ 44include_once(__DIR__ . "/../../lib/security.inc"); 45/** LDIF import */ 46include_once(__DIR__ . "/../../lib/import.inc"); 47 48// start session 49if (isset($_GET['selfservice'])) { 50 // self service uses a different session name 51 session_name('SELFSERVICE'); 52} 53 54// return standard JSON response if session expired 55if (startSecureSession(false, true) === false) { 56 echo json_encode(array( 57 'sessionExpired' => "true" 58 )); 59 die(); 60} 61 62setlanguage(); 63 64$ajax = new Ajax(); 65$ajax->handleRequest(); 66 67/** 68 * Manages all AJAX requests. 69 */ 70class Ajax { 71 72 /** 73 * Manages an AJAX request. 74 */ 75 public function handleRequest() { 76 $this->setHeader(); 77 // check token 78 validateSecurityToken(); 79 $isSelfService = isset($_GET['selfservice']); 80 if (isset($_GET['module']) && isset($_GET['scope']) && in_array($_GET['module'], getAvailableModules($_GET['scope']))) { 81 enforceUserIsLoggedIn(); 82 if (isset($_GET['useContainer']) && ($_GET['useContainer'] == '1')) { 83 $sessionKey = htmlspecialchars($_GET['editKey']); 84 if (!isset($_SESSION[$sessionKey])) { 85 logNewMessage(LOG_ERR, 'Unable to find account container'); 86 die(); 87 } 88 $module = $_SESSION[$sessionKey]->getAccountModule($_GET['module']); 89 $module->handleAjaxRequest(); 90 } 91 else { 92 $module = new $_GET['module']($_GET['scope']); 93 $module->handleAjaxRequest(); 94 } 95 die(); 96 } 97 if (!isset($_GET['function'])) { 98 die(); 99 } 100 $function = $_GET['function']; 101 if (!isset($_POST['jsonInput'])) { 102 die(); 103 } 104 105 $jsonInput = $_POST['jsonInput']; 106 if ($function == 'passwordStrengthCheck') { 107 $this->checkPasswordStrength($jsonInput); 108 die(); 109 } 110 if ($function === 'webauthn') { 111 enforceUserIsLoggedIn(false); 112 $this->manageWebauthn($isSelfService); 113 die(); 114 } 115 if ($function === 'webauthnDevices') { 116 $this->enforceUserIsLoggedInToMainConfiguration(); 117 $this->manageWebauthnDevices(); 118 die(); 119 } 120 enforceUserIsLoggedIn(); 121 if ($function == 'passwordChange') { 122 $this->managePasswordChange($jsonInput); 123 } 124 elseif ($function === 'import') { 125 include_once('../../lib/import.inc'); 126 $importer = new Importer(); 127 ob_start(); 128 $jsonOut = $importer->doImport(); 129 ob_end_clean(); 130 echo $jsonOut; 131 } 132 elseif ($function === 'export') { 133 include_once('../../lib/export.inc'); 134 $attributes = $_POST['attributes']; 135 $baseDn = $_POST['baseDn']; 136 $ending = $_POST['ending']; 137 $filter = $_POST['filter']; 138 $format = $_POST['format']; 139 $includeSystem = ($_POST['includeSystem'] === 'true'); 140 $saveAsFile = ($_POST['saveAsFile'] === 'true'); 141 $searchScope = $_POST['searchScope']; 142 $exporter = new Exporter($baseDn, $searchScope, $filter, $attributes, $includeSystem, $saveAsFile, $format, $ending); 143 ob_start(); 144 $jsonOut = $exporter->doExport(); 145 ob_end_clean(); 146 echo $jsonOut; 147 } 148 elseif ($function === 'upload') { 149 include_once('../../lib/upload.inc'); 150 $typeManager = new \LAM\TYPES\TypeManager(); 151 $uploader = new \LAM\UPLOAD\Uploader($typeManager->getConfiguredType($_GET['typeId'])); 152 ob_start(); 153 $jsonOut = $uploader->doUpload(); 154 ob_end_clean(); 155 echo $jsonOut; 156 } 157 elseif ($function === 'dnselection') { 158 ob_start(); 159 $jsonOut = $this->dnSelection(); 160 ob_end_clean(); 161 echo $jsonOut; 162 } 163 elseif ($function === 'webauthnOwnDevices') { 164 $this->manageWebauthnOwnDevices(); 165 } 166 } 167 168 /** 169 * Sets JSON HTTP header. 170 */ 171 private static function setHeader() { 172 if (!headers_sent()) { 173 header('Content-Type: application/json; charset=utf-8'); 174 } 175 } 176 177 /** 178 * Manages a password change request on the edit account page. 179 * 180 * @param array $input input parameters 181 */ 182 private static function managePasswordChange($input) { 183 $sessionKey = htmlspecialchars($_GET['editKey']); 184 $return = $_SESSION[$sessionKey]->setNewPassword($input); 185 echo json_encode($return); 186 } 187 188 /** 189 * Checks if a password is accepted by LAM's password policy. 190 * 191 * @param array $input input parameters 192 */ 193 private function checkPasswordStrength($input) { 194 $password = $input['password']; 195 $result = checkPasswordStrength($password, null, null); 196 echo json_encode(array("result" => $result)); 197 } 198 199 /** 200 * Manages webauthn requests. 201 * 202 * @param bool $isSelfService request is from self service 203 */ 204 private function manageWebauthn($isSelfService) { 205 include_once __DIR__ . '/../../lib/webauthn.inc'; 206 if ($isSelfService) { 207 $userDN = lamDecrypt($_SESSION['selfService_clientDN'], 'SelfService'); 208 } 209 else { 210 $userDN = $_SESSION['ldap']->getUserName(); 211 } 212 $webauthnManager = new WebauthnManager(); 213 $isRegistered = $webauthnManager->isRegistered($userDN); 214 if (!$isRegistered) { 215 $registrationObject = $webauthnManager->getRegistrationObject($userDN, $isSelfService); 216 $_SESSION['webauthn_registration'] = json_encode($registrationObject); 217 echo json_encode( 218 array( 219 'action' => 'register', 220 'registration' => $registrationObject 221 ), 222 JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE 223 ); 224 } 225 else { 226 $authenticationObject = $webauthnManager->getAuthenticationObject($userDN, $isSelfService); 227 $_SESSION['webauthn_authentication'] = json_encode($authenticationObject); 228 echo json_encode( 229 array( 230 'action' => 'authenticate', 231 'authentication' => $authenticationObject 232 ), 233 JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE 234 ); 235 } 236 die(); 237 } 238 239 /** 240 * Webauthn device management. 241 */ 242 private function manageWebauthnDevices() { 243 $action = $_POST['action']; 244 if ($action === 'search') { 245 $searchTerm = $_POST['searchTerm']; 246 if (!empty($searchTerm)) { 247 $this->manageWebauthnDevicesSearch($searchTerm); 248 } 249 } 250 elseif ($action === 'delete') { 251 $dn = $_POST['dn']; 252 $credentialId = $_POST['credentialId']; 253 if (!empty($dn) && !empty($credentialId)) { 254 $this->manageWebauthnDevicesDelete($dn, $credentialId); 255 } 256 } 257 } 258 259 /** 260 * Searches for webauthn devices and prints the results as html. 261 * 262 * @param string $searchTerm search term 263 */ 264 private function manageWebauthnDevicesSearch($searchTerm) { 265 include_once __DIR__ . '/../../lib/webauthn.inc'; 266 $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); 267 $results = $database->searchDevices('%' . $searchTerm . '%'); 268 $row = new htmlResponsiveRow(); 269 $row->addVerticalSpacer('0.5rem'); 270 if (empty($results)) { 271 $row->add(new htmlStatusMessage('INFO', _('No devices found.')), 12); 272 } 273 else { 274 $titles = array( 275 _('User'), 276 _('Registration'), 277 _('Last use'), 278 _('Delete') 279 ); 280 $data = array(); 281 $id = 0; 282 foreach ($results as $result) { 283 $delButton = new htmlButton('deleteDevice' . $id, 'delete.png', true); 284 $delButton->addDataAttribute('credential', $result['credentialId']); 285 $delButton->addDataAttribute('dn', $result['dn']); 286 $delButton->addDataAttribute('dialogtitle', _('Remove device')); 287 $delButton->addDataAttribute('oktext', _('Ok')); 288 $delButton->addDataAttribute('canceltext', _('Cancel')); 289 $delButton->setCSSClasses(array('webauthn-delete')); 290 $data[] = array( 291 new htmlOutputText($result['dn']), 292 new htmlOutputText(date('Y-m-d H:i:s', $result['registrationTime'])), 293 new htmlOutputText(date('Y-m-d H:i:s', $result['lastUseTime'])), 294 $delButton 295 ); 296 $id++; 297 } 298 $table = new htmlResponsiveTable($titles, $data); 299 $row->add($table, 12); 300 } 301 $row->addVerticalSpacer('2rem'); 302 $tabindex = 10000; 303 ob_start(); 304 $row->generateHTML('none', array(), array(), false, $tabindex, null); 305 $content = ob_get_contents(); 306 ob_end_clean(); 307 echo json_encode(array('content' => $content)); 308 } 309 310 /** 311 * Deletes a webauthn device. 312 * 313 * @param string $dn user DN 314 * @param string $credentialId base64 encoded credential id 315 */ 316 private function manageWebauthnDevicesDelete($dn, $credentialId) { 317 include_once __DIR__ . '/../../lib/webauthn.inc'; 318 $database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite(); 319 $success = $database->deleteDevice($dn, $credentialId); 320 if ($success) { 321 $message = new htmlStatusMessage('INFO', _('The device was deleted.')); 322 } 323 else { 324 $message = new htmlStatusMessage('ERROR', _('The device was not found.')); 325 } 326 $row = new htmlResponsiveRow(); 327 $row->addVerticalSpacer('0.5rem'); 328 $row->add($message, 12); 329 $row->addVerticalSpacer('2rem'); 330 ob_start(); 331 $tabindex = 50000; 332 $row->generateHTML('none', array(), array(), true, $tabindex, null); 333 $content = ob_get_contents(); 334 ob_end_clean(); 335 echo json_encode(array('content' => $content)); 336 } 337 338 /** 339 * Manages requests to setup user's own webauthn devices. 340 */ 341 private function manageWebauthnOwnDevices() { 342 $action = $_POST['action']; 343 $dn = $_POST['dn']; 344 $sessionDn = $_SESSION['ldap']->getUserName(); 345 if ($sessionDn !== $dn) { 346 logNewMessage(LOG_ERR, 'WebAuthn delete canceled, DN does not match.'); 347 die(); 348 } 349 if ($action === 'delete') { 350 $credentialId = $_POST['credentialId']; 351 $this->manageWebauthnDevicesDelete($sessionDn, $credentialId); 352 } 353 } 354 355 /** 356 * Handles DN selection fields. 357 * 358 * @return string JSON output 359 */ 360 private function dnSelection() { 361 $dn = trim($_POST['dn']); 362 if (empty($dn) || !get_preg($dn, 'dn')) { 363 $dnList = $this->getDefaultDns(); 364 $dn = null; 365 } 366 else { 367 $dnList = $this->getSubDns($dn); 368 } 369 $html = $this->buildDnSelectionHtml($dnList, $dn); 370 return json_encode(array('dialogData' => $html)); 371 } 372 373 /** 374 * Returns a list of default DNs from account types + tree suffix. 375 * 376 * @return string[] default DNs 377 */ 378 private function getDefaultDns() { 379 $typeManager = new TypeManager(); 380 $baseDnList = array(); 381 foreach ($typeManager->getConfiguredTypes() as $type) { 382 $suffix = $type->getSuffix(); 383 if (!empty($suffix)) { 384 $baseDnList[] = $suffix; 385 } 386 } 387 $treeSuffix = $_SESSION['config']->get_Suffix('tree'); 388 if (!empty($treeSuffix)) { 389 $baseDnList[] = $suffix; 390 } 391 $baseDnList = array_unique($baseDnList); 392 usort($baseDnList, 'compareDN'); 393 return $baseDnList; 394 } 395 396 /** 397 * Returns the HTML to build the DN selection list. 398 * 399 * @param string[] $dnList DN list 400 * @param string $currentDn current DN 401 */ 402 private function buildDnSelectionHtml($dnList, $currentDn) { 403 $fieldId = trim($_POST['fieldId']); 404 $mainRow = new htmlResponsiveRow(); 405 $onclickUp = 'window.lam.html.updateDnSelection(this, \'' 406 . htmlspecialchars($fieldId) . '\', \'' . getSecurityTokenName() . '\', \'' 407 . getSecurityTokenValue() . '\')'; 408 if (!empty($currentDn)) { 409 $row = new htmlResponsiveRow(); 410 $row->addDataAttribute('dn', $currentDn); 411 $text = new htmlOutputText($currentDn); 412 $text->setIsBold(true); 413 $row->add($text, 12, 9); 414 $row->setCSSClasses(array('text-right')); 415 $buttonId = base64_encode($currentDn); 416 $buttonId = str_replace('=', '', $buttonId); 417 $button = new htmlButton($buttonId, _('Ok')); 418 $button->setIconClass('okButton'); 419 $button->setOnClick('window.lam.html.selectDn(this, \'' . htmlspecialchars($fieldId) . '\')'); 420 $row->add($button, 12, 3); 421 $mainRow->add($row, 12); 422 // back up 423 $row = new htmlResponsiveRow(); 424 $row->addDataAttribute('dn', extractDNSuffix($currentDn)); 425 $text = new htmlLink('..', '#'); 426 $text->setCSSClasses(array('bold')); 427 $text->setOnClick($onclickUp); 428 $row->add($text, 12, 9); 429 $row->setCSSClasses(array('text-right')); 430 $buttonId = base64_encode('..'); 431 $buttonId = str_replace('=', '', $buttonId); 432 $button = new htmlButton($buttonId, _('Up')); 433 $button->setIconClass('upButton'); 434 $button->setOnClick($onclickUp); 435 $row->add($button, 12, 3); 436 $mainRow->add($row, 12); 437 } 438 foreach ($dnList as $dn) { 439 $row = new htmlResponsiveRow(); 440 $row->addDataAttribute('dn', $dn); 441 $link = new htmlLink($dn, '#'); 442 $link->setOnClick($onclickUp); 443 $row->add($link, 12, 9); 444 $row->setCSSClasses(array('text-right')); 445 $buttonId = base64_encode($dn); 446 $buttonId = str_replace('=', '', $buttonId); 447 $button = new htmlButton($buttonId, _('Ok')); 448 $button->setIconClass('okButton'); 449 $button->setOnClick('window.lam.html.selectDn(this, \'' . htmlspecialchars($fieldId) . '\')'); 450 $row->add($button, 12, 3); 451 $mainRow->add($row, 12); 452 } 453 $tabindex = 1000; 454 ob_start(); 455 parseHtml(null, $mainRow, array(), false, $tabindex, 'user'); 456 $out = ob_get_contents(); 457 ob_end_clean(); 458 return $out; 459 } 460 461 /** 462 * Returns the sub DNs of given DN. 463 * 464 * @param string $dn DN 465 * @return string[] sub DNs 466 */ 467 private function getSubDns($dn) { 468 $dnEntries = ldapListDN($dn); 469 $dnList = array(); 470 foreach ($dnEntries as $entry) { 471 $dnList[] = $entry['dn']; 472 } 473 usort($dnList, 'compareDN'); 474 return $dnList; 475 } 476 477 /** 478 * Checks if the user entered the configuration master password. 479 * Dies if password is not set. 480 */ 481 private function enforceUserIsLoggedInToMainConfiguration() { 482 if (!isset($_SESSION['cfgMain'])) { 483 $cfg = new LAMCfgMain(); 484 } 485 else { 486 $cfg = $_SESSION['cfgMain']; 487 } 488 if (isset($_SESSION["mainconf_password"]) && ($cfg->checkPassword($_SESSION["mainconf_password"]))) { 489 return; 490 } 491 die(); 492 } 493 494} 495 496 497?> 498