/** * Method is called before user data is stored in the database. * * Changes the password in LDAP if the user changed their password. * * @param array $user Holds the old user data. * @param boolean $isNew True if a new user is stored. * @param array $new Holds the new user data. * * @return boolean Cancels the save if False. * * @since 2.0 */ public function onUserBeforeSave($user, $isNew, $new) { if ($isNew) { // We dont want to deal with new users here return; } // Get username and password to use for authenticating with Ldap $username = SHUtilArrayhelper::getValue($user, 'username', false, 'string'); $password = SHUtilArrayhelper::getValue($new, 'password_clear', null, 'string'); if (!empty($password)) { $auth = array( 'authenticate' => SHLdap::AUTH_USER, 'username' => $username, 'password' => $password ); try { // We will double check the password for double safety (breaks password reset if on) $authenticate = $this->params->get('authenticate', 0); // Get the user adapter then set the password on it $adapter = SHFactory::getUserAdapter($auth); $adapter->setPassword( $password, SHUtilArrayhelper::getValue($new, 'current-password', null, 'string'), $authenticate ); SHLog::add(JText::sprintf('PLG_LDAP_PASSWORD_INFO_12411', $username), 12411, JLog::INFO, 'ldap'); } catch (Exception $e) { // Log and Error out SHLog::add($e, 12401, JLog::ERROR, 'ldap'); return false; } } }
/** * Create the user to LDAP (before onUserBeforeSave). * * @param array $user Populated LDAP attributes from the form. * * @return boolean Cancels the user creation to Joomla if False. * * @since 2.0 */ public function onUserCreation($user) { try { $dn = null; $attributes = array(); // Populate defaults for the mandatory $mandatory = array( 'username' => SHUtilArrayhelper::getValue($user, 'username'), 'password' => SHUtilArrayhelper::getValue($user, 'password_clear'), 'email' => SHUtilArrayhelper::getValue($user, 'email'), 'name' => SHUtilArrayhelper::getValue($user, 'name') ); // Include the helper file only if it exists if ($this->helper = $this->_getHelperFile()) { // Calculate the correct domain to insert user on if (method_exists($this->helper, 'getDomain')) { $this->domain = $this->helper->getDomain($user); } } $fields = $this->_getXMLFields(); // Loops around everything in the template XML foreach ($fields as $key => $value) { // Convert the value to a string $stringValue = (string) $value; // Convert the key to a string $stringKey = (string) $key; $name = (string) $value->attributes()->name; if ($stringKey == 'dn') { $name = 'mandatory' . $stringKey; // The dn which isn't an array $attribute =& $dn; } elseif ($stringKey == 'username' || $stringKey == 'password' || $stringKey == 'email' || $stringKey == 'name') { $name = 'mandatory' . $stringKey; // The mandatory fields use something a bit different $attribute =& $mandatory[$stringKey]; } else { // Standard multi-array attributes if (!isset($attributes[$name])) { $attributes[$name] = array(); } $attribute =& $attributes[$name][]; } // Get the value of the attributes using a variety of types switch ((string) $value->attributes()->type) { case 'form': $attribute = $user[$stringValue]; break; case 'string': $attribute = $stringValue; break; case 'eval': $attribute = $this->_execEval($stringValue, $user); break; case 'helper': $method = 'get' . (string) $name; $attribute = $this->helper->{$method}($user); break; } } $credentials = array( 'username' => $mandatory['username'], 'password' => $mandatory['password'], 'domain' => $this->domain, 'dn' => $dn ); // Kill any previous adapters for this user (though this plugin should be ordered first!!) SHFactory::$adapters[strtolower($user['username'])] = null; // Create an adapter and save core attributes $adapter = SHFactory::getUserAdapter($credentials, 'ldap', array('isNew' => true)); // Add core Joomla fields $adapter->setAttributes( array( 'username' => $mandatory['username'], 'password' => $mandatory['password'], 'fullname' => $mandatory['name'], 'email' => $mandatory['email'] ) ); // Add extra fields based from the template xml $adapter->setAttributes($attributes); // Create the LDAP user now SHLdapHelper::commitChanges($adapter, true, true); SHLog::add(JText::sprintf('PLG_LDAP_CREATION_INFO_12821', $mandatory['username']), 12821, JLog::INFO, 'ldap'); $this->username = $mandatory['username']; /* * Call onAfterCreation method in the helper which can be used to run * external scripts (such as creating home directories) and/or adding * groups to the new user. * * This method will be passed: * - $user Values directly from the user registration form. * - $attributes The attributes passed to the LDAP server for creation. * - $adapter The user adapter object. */ if ($this->helper && method_exists($this->helper, 'onAfterCreation')) { $this->helper->onAfterCreation($user, $attributes, $adapter); } return true; } catch (Exception $e) { SHLog::add($e, 12802, JLog::ERROR, 'ldap'); return false; } }
/** * Method is called on user login failure. * * @param array $response The authentication response. * * @return void * * @since 2.0 */ public function onUserLoginFailure($response) { // Check if the attempted login was an Ldap user, if so then fire the event if ($username = SHUtilArrayhelper::getValue($response, 'username', false, 'string')) { // Check if the user exists in the J! database if ($id = JUserHelper::getUserId($username)) { if (SHLdapHelper::isUserLdap($id)) { SHLdapHelper::triggerEvent('onUserLoginFailure', array($response)); } } } }
/** * Method is called before user data is stored in the database. * * Saves profile data to LDAP if a profile form is detected. * * @param array $user Holds the old user data. * @param boolean $isNew True if a new user is stored. * @param array $new Holds the new user data. * * @return boolean Cancels the save if False. * * @since 2.0 */ public function onUserBeforeSave($user, $isNew, $new) { if (!$this->params->get('allow_ldap_save', 1)) { // Not allowed to save back to LDAP return; } // Default the return result to true $result = true; try { // Get username for adapter $username = SHUtilArrayhelper::getValue($user, 'username', false, 'string'); if (empty($username)) { // The old username isn't present so use new username $username = SHUtilArrayhelper::getValue($new, 'username', false, 'string'); } // Include the mandatory Joomla fields (fullname and email) $this->saveMandatoryToLdap($username, $new['name'], $new['email']); // Check there is a profile to save (i.e. this event may not have been called from the profile form) if ($this->use_profile && (isset($new[self::FORM_FIELDS_NAME]) && (count($new[self::FORM_FIELDS_NAME])))) { $xml = $this->getXMLFields(SHUserHelper::getDomainParam($new)); // Only get profile data and enabled elements from the input $profileData = $this->cleanInput($xml, $new[self::FORM_FIELDS_NAME]); // Save the profile back to LDAP $result = $this->saveProfileToLdap($xml, $username, $profileData); } } catch (Exception $e) { SHLog::add($e, 12232, JLog::ERROR, 'ldap'); return false; } return $result; }
/** * Find the correct Ldap parameters based on the authorised and configuration * specified. If found then return the successful Ldap object. * * Note: you can use SHLdap::lastUserDn for the user DN instead of rechecking again. * * @param integer|string $id Optional configuration record ID. * @param Array $authorised Optional authorisation/authentication options (authenticate, username, password). * @param JRegistry $registry Optional override for platform configuration registry. * * @return SHLdap An Ldap object on successful authorisation or False on error. * * @since 2.0 * @throws InvalidArgumentException Invalid configurations * @throws SHExceptionStacked User or configuration issues (may not be important) */ public static function getInstance($id = null, array $authorised = array(), JRegistry $registry = null) { // Get the platform registry config from the factory if required $registry = is_null($registry) ? SHFactory::getConfig() : $registry; // Get the optional authentication/authorisation options $authenticate = SHUtilArrayhelper::getValue($authorised, 'authenticate', self::AUTH_NONE); $username = SHUtilArrayhelper::getValue($authorised, 'username', null); $password = SHUtilArrayhelper::getValue($authorised, 'password', null); // Get all the Ldap configs that are enabled and available $configs = SHLdapHelper::getConfig($id, $registry); // Check if only one configuration result was found if ($configs instanceof JRegistry) { // Wrap this around an array so we can use the same code below $configs = array($configs); } // Keep a record of any exceptions called and only log them after $errors = array(); // Loop around each of the Ldap configs until one authenticates foreach ($configs as $config) { try { // Get a new SHLdap object $ldap = new SHLdap($config); // Check if the authenticate/authentication is successful if ($ldap->authenticate($authenticate, $username, $password)) { // This is the correct configuration so return the new client return $ldap; } } catch (Exception $e) { // Add the error to the stack $errors[] = $e; } unset($ldap); } // Failed to find any configs to match if (count($errors) > 1) { // More than one config caused issues, use the stacked exception throw new SHExceptionStacked(JText::_('LIB_SHLDAP_ERR_10411'), 10411, $errors); } else { // Just rethrow the one exception throw $errors[0]; } }
/** * Returns the string value of the specified value ID. * * @param integer $entry Entry ID. * @param string $attribute Attribute name. * @param integer $value Value ID. * @param mixed $default Default value to return. * * @return string Value or Default * * @since 1.0 */ public function getValue($entry, $attribute, $value, $default = false) { if (($getAttribute = $this->getAttribute($entry, $attribute, false)) === false) { // No such attribute exists return $default; } return SHUtilArrayhelper::getValue($getAttribute, $value, $default); }
/** * Setups the JCrypt object with default keys if not specified then returns it. * * @param array $options Optional override options for keys. * * @return JCrypt The configured JCrypt object. * * @since 2.0 */ public static function getCrypt($options = array()) { $source = strtolower(SHUtilArrayhelper::getValue($options, 'source', 'jconfig', 'string')); if ($source === 'jconfig') { /* * If JConfig has been included then lets check whether the keys * have been imported and if not then use the secret value for now. */ if (class_exists('JConfig')) { $config = new JConfig; if (!isset($options['key'])) { $options['key'] = $config->secret; } } } elseif ($source === 'file') { $file = SHUtilArrayhelper::getValue($options, 'file', '', 'string'); if (file_exists($file)) { $options['key'] = file_get_contents($file); } } $crypt = new JCrypt; // Create some default options $type = SHUtilArrayhelper::getValue($options, 'type', 'simple', 'string'); $key = SHUtilArrayhelper::getValue($options, 'key', 'DEFAULTKEY', 'string'); $crypt->setKey( new JCryptKey( $type, $key, $key ) ); return $crypt; }
/** * Set changes to the attributes within an Ldap distinguished name object. * This method compares the current attribute values against a new changed * set of attribute values and commits the differences. * * @param array $options Optional array of options. * * @return SHAdapterResponseCommits Stores all commit objects and status. * * @since 2.0 * @throws RuntimeException */ public function commitChanges($options = array()) { if ($this->_dn instanceof Exception) { // Do not retry. Ldap configuration or user has problems. throw $this->_dn; } $response = new SHAdapterResponseCommits; if ($this->isNew) { $options['nothrow'] = true; // We only want to create the user $response->commits = array($this->create($options)); return $response; } if (empty($this->_changes)) { // There is nothing to commit return $response; } // If the user write is enabled then we should just try to authenticate now if ($userWrite = SHUtilArrayhelper::getValue($options, 'userWrite', false, 'boolean')) { $this->getId(true); } // Get the current attributes $current = $this->getAttributes(array_keys($this->_changes), false); $deleteEntries = array(); $addEntries = array(); $replaceEntries = array(); // Loop around all changes foreach ($this->_changes as $key => $value) { if ($key === 'dn') { continue; } $return = 0; // Check this attribute for multiple values if (is_array($value)) { /* This is a multiple value attriute and to preserve * order we must replace the whole thing if changes * are required. */ $modification = false; $new = array(); $count = 0; for ($i = 0; $i < count($value); ++$i) { if ($return = self::_checkFieldHelper($current, $key, $count, $value[$i])) { $modification = true; } if ($return !== 3 && $value[$i]) { // We don't want to save deletes $new[] = $value[$i]; ++$count; } } if ($modification) { // We want to delete it first $deleteEntries[$key] = array(); if (count($new)) { // Now lets re-add them $addEntries[$key] = $new; } } } else { /* This is a single value attribute and we now need to * determine if this needs to be ignored, added, * modified or deleted. */ $return = self::_checkFieldHelper($current, $key, 0, $value); // Check if this is a password attribute as the replace needs to be forced if ($key === $this->getPassword(true)) { $replaceEntries[$key] = array($value); } else { switch ($return) { case 1: $replaceEntries[$key] = array($value); break; case 2: $addEntries[$key] = array($value); break; case 3: $deleteEntries[$key] = array(); break; } } } } // We can now commit the changes to the LDAP server for this DN (order MATTERS!). $operations = array('delete' => $deleteEntries, 'add' => $addEntries, 'replace' => $replaceEntries); // Check whether we need to be binded as proxy to write to ldap if (!$userWrite && $this->client->bindStatus !== SHLdap::AUTH_PROXY) { if (!$this->client->proxyBind()) { // Failed to map as a proxy user throw new RuntimeException(JText::_('LIB_SHUSERADAPTERSLDAP_ERR_10901'), 10901); } } if (isset($this->_changes['dn']) && ($this->_changes['dn'] != $this->_dn)) { // TODO: Need to rename the DN using SHLdap::rename() throw new InvalidArgumentException(JText::_('LIB_SHUSERADAPTERSLDAP_ERR_10922'), 10922); } foreach ($operations as $operation => $commit) { // Remove password from this commit unset ($commit[$this->getPassword(true)]); $method = "{$operation}Attributes"; // Check there are some attributes to process for this commit if (count($commit)) { try { // Commit the Ldap attribute operation $this->client->$method($this->_dn, $commit); // Successful commit so say so $response->addCommit( $operation, JText::sprintf( 'LIB_SHUSERADAPTERSLDAP_INFO_10924', $operation, $this->username, preg_replace('/\s+/', ' ', var_export($commit, true)) ) ); // Change the attribute field for this commit $this->_attributes = array_merge($this->_attributes, $commit); if ($operation == 'add') { // Add operation means we need to remove attribute keys from nullAttributes foreach (array_keys($commit) as $k) { if (($index = array_search($k, $this->_nullAttributes)) !== false) { unset ($this->_nullAttributes[$index]); } } } elseif ($operation == 'delete') { // Delete operation means we need to add attribute keys to nullAttributes foreach (array_keys($commit) as $k) { if (array_search($k, $this->_nullAttributes) === false) { $this->_nullAttributes[] = $k; } } } } catch (Exception $e) { // An error happened trying to commit the change so lets log it $response->addCommit( $operation, JText::sprintf( 'LIB_SHUSERADAPTERSLDAP_INFO_10926', $operation, $this->username, preg_replace('/\s+/', ' ', var_export($commit, true)) ), JLog::ERROR, $e ); } } } // Clear the changes even if they failed $this->_changes = array(); return $response; }
/** * This method handles the user adapter authentication and reports * back to the subject. * * @param array $credentials Array holding the user credentials * @param array $options Array of extra options * @param object &$response Authentication response object * * @return boolean Authentication result * * @since 2.0 */ public function onUserAuthenticate($credentials, $options, &$response) { $response->type = self::AUTH_TYPE; if (empty($credentials['password'])) { // Blank passwords not allowed to prevent anonymous binding $response->status = JAuthentication::STATUS_FAILURE; $response->error_message = JText::_('PLG_AUTHENTICATION_SHADAPTER_ERR_12602'); return; } // Check the Shmanic platform has been imported if (!$this->_checkPlatform()) { // Failed to import the platform $response->status = JAuthentication::STATUS_FAILURE; $response->error_message = JText::_('PLG_AUTHENTICATION_SHADAPTER_ERR_12601'); return false; } // Check if a Domain is present which represents a configuration ID if ($domain = SHUtilArrayhelper::getValue($options, 'domain', null, 'cmd')) { // Valid configuration ID (normally for SSO) $credentials['domain'] = $domain; } else { // See if we can get the domain directly from the input if ($domain = JFactory::getApplication()->input->get('login_domain', null, 'cmd')) { $credentials['domain'] = $domain; } } /* * Attempt to authenticate with user adapter. This method will automatically detect * the correct configuration (if multiple ones are specified) and return a * SHUserAdapter* object. If the getid returns empty or it throws an error then * authentication was unsuccessful. */ try { // Setup new user adapter $adapter = SHFactory::getUserAdapter($credentials); // Get the authenticating user dn $id = $adapter->getId(true); // Get the required attributes (this gets core attributes + plugin based) if (!empty($id) && $attributes = $adapter->getAttributes()) { // Report back with success $response->status = JAuthentication::STATUS_SUCCESS; $response->error_message = ''; return true; } // Unable to find user or attributes missing (an error should get thrown already) throw new Exception(JText::_('JGLOBAL_AUTH_NO_USER'), 999); } catch (Exception $e) { // Configuration or authentication failure $response->status = JAuthentication::STATUS_FAILURE; $response->error_message = JText::_('JGLOBAL_AUTH_NO_USER'); // Process a error log even if it could be a simple incorrect user SHLog::add($e, 12621, JLog::ERROR, 'auth'); return; } }