/** * Find a user (eg. look up the user record in database when a login is sent) * * @return mixed user array or FALSE * @throws UnsupportedLoginSecurityLevelException */ public function getUser() { $user = FALSE; $userRecordOrIsValid = FALSE; $enableFrontendSso = TYPO3_MODE === 'FE' && (bool) $this->config['enableFESSO'] && !empty($_SERVER['REMOTE_USER']); // This simple check is the key to prevent your log being filled up with warnings // due to the AJAX calls to maintain the session active if your configuration forces // the authentication stack to always fetch the user: // $TYPO3_CONF_VARS['SVCONF']['auth']['setup']['BE_alwaysFetchUser'] = true; // This is the case, e.g., when using EXT:crawler. if ($this->login['status'] !== 'login' && !$enableFrontendSso) { return $user; } /** @var \Causal\IgLdapSsoAuth\Domain\Repository\ConfigurationRepository $configurationRepository */ $configurationRepository = GeneralUtility::makeInstance('Causal\\IgLdapSsoAuth\\Domain\\Repository\\ConfigurationRepository'); $configurationRecords = $configurationRepository->findAll(); if (count($configurationRecords) === 0) { // Early return since LDAP is not configured static::getLogger()->warning('Skipping LDAP authentication as extension is not yet configured'); $this->cleanUpExtbaseDataMapper(); return FALSE; } foreach ($configurationRecords as $configurationRecord) { Configuration::initialize(TYPO3_MODE, $configurationRecord); if (!Configuration::isEnabledForCurrentHost()) { $msg = sprintf('Configuration record #%s is not enabled for domain %s', $configurationRecord->getUid(), GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY')); static::getLogger()->info($msg); continue; } // Enable feature $userRecordOrIsValid = FALSE; // Single Sign-On authentication if ($enableFrontendSso) { $remoteUser = $_SERVER['REMOTE_USER']; // Strip the domain name if ($pos = strpos($remoteUser, '@')) { $remoteUser = substr($remoteUser, 0, $pos); } elseif ($pos = strrpos($remoteUser, '\\')) { $remoteUser = substr($remoteUser, $pos + 1); } $userRecordOrIsValid = Authentication::ldapAuthenticate($remoteUser); if (is_array($userRecordOrIsValid)) { // Useful for debugging purpose $this->login['uname'] = $remoteUser; } // Authenticate user from LDAP } elseif ($this->login['status'] === 'login' && $this->login['uident']) { // Configuration of authentication service. $loginSecurityLevel = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['loginSecurityLevel']; // normal case // Check if $loginSecurityLevel is set to "challenged" or "superchallenged" and throw an error if the configuration allows it // By default, it will not throw an exception if (isset($this->config['throwExceptionAtLogin']) && $this->config['throwExceptionAtLogin'] == 1) { if ($loginSecurityLevel === 'challenged' || $loginSecurityLevel === 'superchallenged') { $message = "ig_ldap_sso_auth error: current login security level '" . $loginSecurityLevel . "' is not supported."; $message .= " Try to use 'normal' or 'rsa' (highly recommended): "; $message .= "\$GLOBALS['TYPO3_CONF_VARS']['" . TYPO3_MODE . "']['loginSecurityLevel'] = 'rsa';"; $this->cleanUpExtbaseDataMapper(); throw new UnsupportedLoginSecurityLevelException($message, 1324313489); } } // normal case $password = $this->login['uident_text']; try { if ($password !== NULL) { $userRecordOrIsValid = Authentication::ldapAuthenticate($this->login['uname'], $password); } else { // Could not decrypt password $userRecordOrIsValid = FALSE; } } catch (UnresolvedPhpDependencyException $e) { // Possible known exception: 1409566275, LDAP extension is not available for PHP $userRecordOrIsValid = FALSE; } } if (is_array($userRecordOrIsValid)) { $user = $userRecordOrIsValid; break; } elseif ($userRecordOrIsValid) { // Authentication is valid break; } else { $diagnostic = Authentication::getLastAuthenticationDiagnostic(); $info = array('username' => $this->login['uname'], 'remote' => sprintf('%s (%s)', $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST']), 'diagnostic' => $diagnostic, 'configUid' => $configurationRecord->getUid()); static::getLogger()->error('Authentication failed', $info); NotificationUtility::dispatch(__CLASS__, 'authenticationFailed', $info); } // Continue and try with next configuration record... } if (!$user && $userRecordOrIsValid) { $user = $this->fetchUserRecord($this->login['uname']); } if (is_array($user)) { static::getLogger()->debug(sprintf('User found: "%s"', $this->login['uname'])); } $this->cleanUpExtbaseDataMapper(); return $user; }
/** * @test */ public function parentGroupIsNotMerged() { $mapping = <<<EOT \t\t\tpid = 1 \t\t\ttitle = <cn> \t\t\tdescription = LDAP Group <cn> \t\t\tparentGroup = <memberOf> EOT; $expected = $this->typo3GroupFixture; $expected['pid'] = '1'; $expected['title'] = 'Scientists'; $expected['description'] = 'LDAP Group Scientists'; $mapping = Configuration::parseMapping($mapping); $group = Authentication::merge($this->ldapGroupFixture, $this->typo3GroupFixture, $mapping); $this->assertEquals($expected, $group); }
/** * Imports a given user to the TYPO3 database. * * @param array $user Local user information * @param array $ldapUser LDAP user information * @param string $restoreBehavior How to restore users (only for update) * @return array Modified user data * @throws ImportUsersException */ public function import($user, $ldapUser, $restoreBehavior = 'both') { // Store the extra data for later restore and remove it if (isset($user['__extraData'])) { $extraData = $user['__extraData']; unset($user['__extraData']); } if (empty($user['uid'])) { // Set other necessary information for a new user // First make sure to be acting in the right context Configuration::setMode($this->context); $user['username'] = Typo3UserRepository::setUsername($user['username']); $user['password'] = Typo3UserRepository::setRandomPassword(); $typo3Groups = Authentication::getUserGroups($ldapUser, $this->configuration, $this->groupTable); if ($typo3Groups === NULL) { // Required LDAP groups are missing: quit! return $user; } $user = Typo3UserRepository::setUserGroups($user, $typo3Groups); $user = Typo3UserRepository::add($this->userTable, $user); $this->usersAdded++; } else { // Restore user that may have been previously deleted or disabled, depending on chosen behavior // (default to both undelete and re-enable) switch ($restoreBehavior) { case 'enable': $user[$GLOBALS['TCA'][$this->userTable]['ctrl']['enablecolumns']['disabled']] = 0; break; case 'undelete': $user[$GLOBALS['TCA'][$this->userTable]['ctrl']['delete']] = 0; break; case 'nothing': break; default: $user[$GLOBALS['TCA'][$this->userTable]['ctrl']['enablecolumns']['disabled']] = 0; $user[$GLOBALS['TCA'][$this->userTable]['ctrl']['delete']] = 0; } $typo3Groups = Authentication::getUserGroups($ldapUser, $this->configuration, $this->groupTable); $user = Typo3UserRepository::setUserGroups($user, $typo3Groups === NULL ? array() : $typo3Groups); $success = Typo3UserRepository::update($this->userTable, $user); if ($success) { $this->usersUpdated++; } } // Restore the extra data and trigger a signal if (isset($extraData)) { $user['__extraData'] = $extraData; // Hook for processing the extra data if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ig_ldap_sso_auth']['extraDataProcessing'])) { foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ig_ldap_sso_auth']['extraDataProcessing'] as $className) { /** @var $postProcessor \Causal\IgLdapSsoAuth\Utility\ExtraDataProcessorInterface */ $postProcessor = GeneralUtility::getUserObj($className); if ($postProcessor instanceof \Causal\IgLdapSsoAuth\Utility\ExtraDataProcessorInterface) { $postProcessor->processExtraData($this->userTable, $user); } else { throw new ImportUsersException(sprintf('Invalid post-processing class %s. It must implement the \\Causal\\IgLdapSsoAuth\\Utility\\ExtraDataProcessorInterface interface', $className), 1414136057); } } } } return $user; }
/** * Returns the LDAP user groups with information merged with local TYPO3 user groups. * * @param \Causal\IgLdapSsoAuth\Domain\Model\Configuration $configuration * @param string $mode * @return array */ protected function getAvailableUserGroups(\Causal\IgLdapSsoAuth\Domain\Model\Configuration $configuration, $mode) { $userGroups = array(); $config = $mode === 'be' ? Configuration::getBackendConfiguration() : Configuration::getFrontendConfiguration(); $ldapGroups = array(); if (!empty($config['groups']['basedn'])) { $filter = Configuration::replaceFilterMarkers($config['groups']['filter']); $attributes = Configuration::getLdapAttributes($config['groups']['mapping']); $ldapGroups = Ldap::getInstance()->search($config['groups']['basedn'], $filter, $attributes); unset($ldapGroups['count']); } // Populate an array of TYPO3 group records corresponding to the LDAP groups // If a given LDAP group has no associated group in TYPO3, a fresh record // will be created so that $ldapGroups[i] <=> $typo3Groups[i] $typo3GroupPid = Configuration::getPid($config['groups']['mapping']); $table = $mode === 'be' ? 'be_groups' : 'fe_groups'; $typo3Groups = Authentication::getTypo3Groups($ldapGroups, $table, $typo3GroupPid); foreach ($ldapGroups as $index => $ldapGroup) { $userGroup = Authentication::merge($ldapGroup, $typo3Groups[$index], $config['groups']['mapping']); // Attempt to free memory by unsetting fields which are unused in the view $keepKeys = array('uid', 'pid', 'deleted', 'title', 'tx_igldapssoauth_dn'); $keys = array_keys($userGroup); foreach ($keys as $key) { if (!in_array($key, $keepKeys)) { unset($userGroup[$key]); } } $userGroups[] = $userGroup; } return $userGroups; }
/** * Performs the synchronization of LDAP users according to selected parameters. * * @return boolean Returns TRUE on successful execution, FALSE on error * @throws ImportUsersException */ public function execute() { // Assemble a list of configuration and contexts for import /** @var \Causal\IgLdapSsoAuth\Domain\Repository\ConfigurationRepository $configurationRepository */ $configurationRepository = GeneralUtility::makeInstance('Causal\\IgLdapSsoAuth\\Domain\\Repository\\ConfigurationRepository'); if (empty($this->configuration)) { $ldapConfigurations = $configurationRepository->findAll(); } else { $configuration = $configurationRepository->findByUid($this->configuration); $ldapConfigurations = array(); if ($configuration !== NULL) { $ldapConfigurations[] = $configuration; } } if ($this->context === 'both') { $executionContexts = array('fe', 'be'); } else { $executionContexts = array($this->context); } $mode = $this->getMode(); // Start a database transaction with all our changes // Syntax is compatible with MySQL, Oracle, MSSQL and PostgreSQL $this->getDatabaseConnection()->sql_query('START TRANSACTION'); // Loop on each configuration and context and import the related users $failures = 0; foreach ($ldapConfigurations as $configuration) { foreach ($executionContexts as $aContext) { /** @var \Causal\IgLdapSsoAuth\Utility\UserImportUtility $importUtility */ $importUtility = GeneralUtility::makeInstance('Causal\\IgLdapSsoAuth\\Utility\\UserImportUtility', $configuration, $aContext); $config = $importUtility->getConfiguration(); if (empty($config['users']['filter'])) { // Current context is not configured for this LDAP configuration record static::getLogger()->debug(sprintf('Configuration record %s is not configured for context "%s"', $configuration->getUid(), $aContext)); unset($importUtility); continue; } // Start by connecting to the designated LDAP/AD server $success = Ldap::getInstance()->connect(Configuration::getLdapConfiguration()); // Proceed with import if successful if (!$success) { $failures++; unset($importUtility); continue; } $ldapUsers = $importUtility->fetchLdapUsers(); // Consider that fetching no users from LDAP is an error if (count($ldapUsers) === 0) { static::getLogger()->error(sprintf('No users (%s) found for configuration record %s', $aContext, $configuration->getUid())); $failures++; } else { // Disable or delete users, according to settings if ($this->missingUsersHandling === 'disable') { static::getLogger()->debug(sprintf('Disabling users (%s) for configuration record %s', $aContext, $configuration->getUid())); $importUtility->disableUsers(); } elseif ($this->missingUsersHandling === 'delete') { static::getLogger()->debug(sprintf('Deleting users (%s) for configuration record %s', $aContext, $configuration->getUid())); $importUtility->deleteUsers(); } // Proceed with import (handle partial result sets until every LDAP record has been returned) do { $typo3Users = $importUtility->fetchTypo3Users($ldapUsers); // Loop on all users and import them foreach ($ldapUsers as $index => $aUser) { if ($mode === 'sync' && empty($typo3Users[$index]['uid'])) { // New LDAP user => skip it since only existing TYPO3 users should get synchronized continue; } // Merge LDAP and TYPO3 information $user = Authentication::merge($aUser, $typo3Users[$index], $config['users']['mapping']); // Import the user using information from LDAP $importUtility->import($user, $aUser, $this->restoredUsersHandling); } static::getLogger()->info(sprintf('Configuration record %s: processed %s LDAP users (%s)', $configuration->getUid(), count($ldapUsers), $aContext)); // Free memory before going on $typo3Users = NULL; $ldapUsers = NULL; $ldapUsers = $importUtility->hasMoreLdapUsers() ? $importUtility->fetchLdapUsers(TRUE) : array(); } while (count($ldapUsers) > 0); } // Clean up unset($importUtility); Ldap::getInstance()->disconnect(); } } // If some failures were registered, rollback the whole transaction and report error if ($failures > 0) { $this->getDatabaseConnection()->sql_query('ROLLBACK'); $message = 'Some or all imports failed. Synchronisation was aborted. Check your settings or your network connection'; static::getLogger()->error($message); throw new ImportUsersException($message, 1410774015); } else { // Everything went fine, commit the changes $this->getDatabaseConnection()->sql_query('COMMIT'); } return TRUE; }
/** * Returns the title for a given user. * * @param array $ldap_user * @param array $mapping * @return null|string * @deprecated since 3.0, will be removed in 3.2 */ public static function get_title($ldap_user = array(), $mapping = array()) { if (!$mapping) { return NULL; } if (isset($mapping['title']) && preg_match('`<([^$]*)>`', $mapping['title'], $attribute)) { if ($attribute[1] === 'dn') { return $ldap_user[$attribute[1]]; } return Authentication::replaceLdapMarkers($mapping['title'], $ldap_user); } return NULL; }