Invision Power Services, Inc. * @copyright (c) 2001 - 2016 Invision Power Services, Inc. * @license http://www.invisionpower.com/legal/standards/ * @package IPS Community Suite * @since 13 Mar 2013 * @version SVN_VERSION_NUMBER */ namespace IPS\Login; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Internal Login Handler */ class _Internal extends LoginAbstract { /** * @brief Authentication types */ public $authTypes; /** * Initiate * * @return void */ public function init() { $this->authTypes = $this->settings['auth_types'] ?: \IPS\Login::AUTH_TYPE_USERNAME; } /** * Authenticate * * @param array $values Values from from * @return \IPS\Member * @throws \IPS\Login\Exception */ public function authenticate( $values ) { /* Get member(s) */ $members = array(); if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME ) { $_member = \IPS\Member::load( $values['auth'], 'name', NULL ); if ( $_member->member_id ) { $members[] = $_member; } $_legacyMember = \IPS\Member::load( \IPS\Request::legacyEscape( $values['auth'] ), 'name', NULL ); if ( $_legacyMember->member_id ) { $members[] = $_legacyMember; } } if ( $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL ) { $_member = \IPS\Member::load( $values['auth'], 'email' ); if ( $_member->member_id ) { $members[] = $_member; } $_legacyMember = \IPS\Member::load( \IPS\Request::legacyEscape( $values['auth'] ), 'email' ); if ( $_legacyMember->member_id ) { $members[] = $_legacyMember; } } /* If we didn't match any, throw an exception */ if ( empty( $members ) ) { throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $this->getLoginType( $this->authTypes ) ) ) ) ), \IPS\Login\Exception::NO_ACCOUNT ); } /* Check the password for each possible account */ foreach ( $members as $member ) { if ( \IPS\Login::compareHashes( $member->members_pass_hash, $member->encryptedPassword( $values['password'] ) ) ) { /* If it's the old style, convert it to the new */ if ( mb_strlen( $member->members_pass_salt ) !== 22 ) { $member->members_pass_salt = $member->generateSalt(); $member->members_pass_hash = $member->encryptedPassword( $values['password'] ); $member->save(); } /* Return */ return $member; } } /* Still here? Throw a password incorrect exception */ throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member ); } /** * ACP Settings Form * * @param string $url URL to redirect user to after successful submission * @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field * @code return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... ); * @endcode */ public function acpForm() { return array( 'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', $this->settings['auth_types'], TRUE, array( 'options' => array( \IPS\Login::AUTH_TYPE_USERNAME => 'username', \IPS\Login::AUTH_TYPE_EMAIL => 'email_address', \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email', ) ) ) ); } /** * [Node] Get whether or not this node is enabled * * @note Return value NULL indicates the node cannot be enabled/disabled * @return bool|null */ protected function get__enabled() { return TRUE; } /** * [Node] Get whether or not this node is locked to current enabled/disabled status * * @note Return value NULL indicates the node cannot be enabled/disabled * @return bool|null */ protected function get__locked() { return TRUE; } /** * Can a member sign in with this login handler? * Used to ensure when a user disassociates a social login that they have some other way of logging in * * @param \IPS\Member $member The member * @return bool */ public function canProcess( \IPS\Member $member ) { if ( !$member->members_pass_hash ) { return FALSE; } if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME and $member->name ) { return TRUE; } if ( $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL and $member->email ) { return TRUE; } return FALSE; } /** * Can a member change their email/password with this login handler? * * @param string $type 'username' or 'email' or 'password' * @param \IPS\Member $member The member * @return bool */ public function canChange( $type, \IPS\Member $member ) { /* We can only change the internal password if we haave one (i.e. we didn't sign in with a social service) because we need the existing password */ if ( $type === 'password' ) { return (bool) $member->members_pass_hash; } /* But we can always change username and email */ return TRUE; } /** * Email is in use? * Used when registering or changing an email address to check the new one is available * * @param string $email Email Address * @param \IPS\Member|NULL $exclude Member to exclude * @return bool|NULL Boolean indicates if email is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API */ public function emailIsInUse( $email, \IPS\Member $exclude=NULL ) { $member = \IPS\Member::load( $email, 'email' ); return (bool) ( $member->member_id and $member != $exclude ); } /** * Username is in use? * Used when registering or changing an username to check the new one is available * * @param string $username Username * @return bool|NULL Boolean indicates if username is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API */ public function usernameIsInUse( $username ) { $member = \IPS\Member::load( $username, 'name' ); return (bool) $member->member_id; } /** * Change Email Address * * @param \IPS\Member $member The member * @param string $oldEmail Old Email Address * @param string $newEmail New Email Address * @return void * @throws \Exception */ public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail ) { $member->email = $newEmail; $member->save(); $member->memberSync( 'onEmailChange', array( $newEmail, $oldEmail ) ); } /** * Change Password * * @param \IPS\Member $member The member * @param string $newPassword New Password * @return void * @throws \Exception */ public function changePassword( \IPS\Member $member, $newPassword ) { /* Clear any current sessions for this user. This resolves an issue where a user resets their password in one location, but a malicious user with access to their account can retain access in another location. */ \IPS\Db::i()->delete( 'core_sessions', array( 'member_id=?', $member->member_id ) ); \IPS\Db::i()->delete( 'core_sys_cp_sessions', array( 'session_member_id=?', $member->member_id ) ); $member->members_pass_salt = $member->generateSalt(); $member->members_pass_hash = $member->encryptedPassword( $newPassword ); $member->member_login_key = ''; /* When we reset the login key and call checkLoginKey(), it will save for us. Note that we are NOT sending an updated pass_hash cookie here, and that is instead the responsibility of any controllers that result in the password being updated. This is a central class and we may not be updating the current viewing user's password at this point. */ $member->checkLoginKey(); $member->memberSync( 'onPassChange', array( $newPassword ) ); } /** * Change Username * * @param \IPS\Member $member The member * @param string $oldUsername Old Username * @param string $newUsername New Username * @return void * @throws \Exception */ public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername ) { $member->name = $newUsername; $member->save(); } }